CrabUI
Loading...
Searching...
No Matches
AutomaticTestRunner.cs
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using System.Diagnostics;
6using System.Runtime.CompilerServices;
7using System.IO;
8
9using Barotrauma;
10using Microsoft.Xna.Framework;
11using Microsoft.Xna.Framework.Input;
12using Microsoft.Xna.Framework.Graphics;
13using HarmonyLib;
14using CrabUI;
15using System.Text;
16using System.Threading;
17using System.Xml;
18using System.Xml.Linq;
19
20namespace CrabUITest
21{
22 public class IgnoreAttribute : System.Attribute { }
23 public class HideFromAutoTestRunnerAttribute : System.Attribute { }
24
25 public enum TestStatus
26 {
27 Passed,
28 Failed,
29 Unknown,
30 Stashed,
31 }
32 public record TestReport(Type TestClass, MethodInfo TestMethod, TestStatus Status, string DeepCompare = "");
33
34 public class TestingConfig
35 {
36 public static string ConfigPath => Path.Combine(CUITest.IgnoredDataPath, "Config.xml");
37
38 public event Action OnChanged;
39
40 private string autorun = "";
41 public string Autorun
42 {
43 get => autorun;
44 set
45 {
46 autorun = value ?? "";
47 Save();
48 OnChanged?.Invoke();
49 }
50 }
51
52 public void Load()
53 {
54 if (!File.Exists(ConfigPath)) return;
55 XDocument xdoc = XDocument.Load(ConfigPath);
56 XElement root = xdoc.Element("Autorun");
57 Autorun = root?.Value;
58 }
59
60 public void Save()
61 {
62 XDocument xdoc = new XDocument();
63 xdoc.Add(new XElement("Autorun", Autorun ?? ""));
64 xdoc.Save(ConfigPath);
65 }
66 }
67
68 public class AutomaticTestRunner
69 {
70 public CUIComponent TestChamber => CUITest.TestChamber;
71 public static string TestStashPath => Path.Combine(CUITest.IgnoredDataPath, "Stash");
72
73 public AutoTestGUI GUI;
74 public TestingConfig Config;
75
76
77 public IEnumerable<Type> GetAllTestClasses()
78 {
79 Assembly targetAssembly = Assembly.GetAssembly(typeof(FillMethods));
80
81 return targetAssembly.GetTypes()
82 .Where(t => t.IsSubclassOf(typeof(FillMethods)));
83 }
84
85 public IEnumerable<MethodInfo> GetAllTestMethodsForAClass(Type type)
86 {
87 return type.GetMethods(BindingFlags.Public | BindingFlags.Static)
88 .Where(mi => FillMethods.IsTestMethod(mi));
89 }
90
91 public IEnumerable<MethodInfo> GetAllAutoTestMethodsForAClass(Type type)
92 {
93 return type.GetMethods(BindingFlags.Public | BindingFlags.Static)
94 .Where(mi => FillMethods.IsTestMethod(mi) && !Attribute.IsDefined(mi, typeof(HideFromAutoTestRunnerAttribute)));
95 }
96
97 public string[] GetClassMethodPairs()
98 {
99 List<string> all = new();
100 foreach (Type type in GetAllTestClasses())
101 {
102 foreach (MethodInfo mi in GetAllTestMethodsForAClass(type))
103 {
104 all.Add($"{type.Name}.{mi.Name}");
105 }
106 }
107 return all.ToArray();
108 }
109
110 public static (Type, MethodInfo) DeconstructFullName(string fullName)
111 {
112 string typeName = fullName.Split('.').ElementAtOrDefault(0);
113 string methodName = fullName.Split('.').ElementAtOrDefault(1);
114
115 Assembly targetAssembly = Assembly.GetAssembly(typeof(FillMethods));
116
117 Type type = targetAssembly.GetTypes().Find(T => T.Name == typeName);
118 if (type == null) return (null, null);
119
120 MethodInfo method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static);
121 return (type, method);
122 }
123
124 // Could be in it's own class
125 public string DeepCompare(string real, string stashed)
126 {
127 StringBuilder result = new StringBuilder("‖color:gray‖");
128
129 int minLength = Math.Min(real.Length, stashed.Length);
130
131 bool ok = true;
132 for (int i = 0; i < minLength; i++)
133 {
134 if (ok)
135 {
136 if (real[i] != stashed[i]) { ok = false; result.Append("‖end‖‖color:cyan‖"); }
137 }
138 else
139 {
140 if (real[i] == stashed[i]) { ok = true; result.Append("‖end‖‖color:gray‖"); }
141 }
142
143 result.Append(real[i]);
144 }
145 result.Append("‖end‖");
146
147 return result.ToString();
148 }
149
150 private MethodInfo activeTest; public MethodInfo ActiveTest
151 {
152 get => activeTest;
153 set
154 {
155 if (activeTest == value)
156 {
157 CUITest.ClearTestChamber();
158 return;
159 }
160
161 activeTest = value;
162
163 if (activeTest != null)
164 {
165 if (TestChamber == null) CUITest.CreateTestChamber();
166 if (TestChamber.Parent == null) CUITest.OpenTestChamber();
167
168 CUITest.ClearTestChamber();
169 }
170 else
171 {
172 CUITest.ClearTestChamber();
173 if (TestChamber.Parent != null) CUITest.CloseTestChamber();
174 }
175 }
176 }
177
178 public event Action<TestReport> OnTestReport;
179
180 public void StopTest() => ActiveTest = null;
181
182 public void RunAllTests()
183 {
184 foreach (Type testClass in GetAllTestClasses())
185 {
186 foreach (MethodInfo testMethod in GetAllAutoTestMethodsForAClass(testClass))
187 {
188 RunTest(testClass, testMethod);
189 }
190 }
191 }
192
193 public void ClearStash()
194 {
195 foreach (Type testClass in GetAllTestClasses())
196 {
197 foreach (MethodInfo testMethod in GetAllTestMethodsForAClass(testClass))
198 {
199 if (!Directory.Exists(Path.Combine(TestStashPath, testClass.Name)))
200 {
201 Directory.CreateDirectory(Path.Combine(TestStashPath, testClass.Name));
202 }
203
204 string testPath = Path.Combine(TestStashPath, testClass.Name, $"{testMethod.Name}.xml");
205
206 if (File.Exists(testPath))
207 {
208 File.Delete(testPath);
209 OnTestReport?.Invoke(new TestReport(testClass, testMethod, TestStatus.Unknown));
210 }
211 }
212 }
213 }
214
215 public void RunTest(string fullName)
216 {
217 (Type testClass, MethodInfo testMethod) = DeconstructFullName(fullName);
218 RunTest(testClass, testMethod);
219 }
220
221 public void RunTest(Type testClass, MethodInfo testMethod)
222 {
223 Action<CUIComponent> test = testMethod.CreateDelegate<Action<CUIComponent>>();
224
225 ActiveTest = testMethod;
226
227 try
228 {
229 test(TestChamber);
230 TestChamber.MainComponent.Step();
231
232
233 if (!Directory.Exists(Path.Combine(TestStashPath, testClass.Name)))
234 {
235 Directory.CreateDirectory(Path.Combine(TestStashPath, testClass.Name));
236 }
237
238 string testPath = Path.Combine(TestStashPath, testClass.Name, $"{testMethod.Name}.xml");
239
240 if (!File.Exists(testPath))
241 {
242 OnTestReport?.Invoke(new TestReport(testClass, testMethod, TestStatus.Unknown));
243 return;
244 }
245
246 string stashed = "";
247 using (StreamReader reader = new StreamReader(testPath))
248 {
249 stashed = reader.ReadToEnd().Trim();
250 }
251
252 string real = TestChamber.Serialize(CUIAttribute.Calculated).Trim();
253 if (stashed == real)
254 {
255 OnTestReport?.Invoke(new TestReport(testClass, testMethod, TestStatus.Passed));
256 }
257 else
258 {
259 OnTestReport?.Invoke(new TestReport(testClass, testMethod, TestStatus.Failed, DeepCompare(real, stashed)));
260 }
261 }
262 catch (Exception e) { CUITest.Log(e, Color.Orange); }
263 }
264
265 public void StashTest(string fullName)
266 {
267 (Type testClass, MethodInfo testMethod) = DeconstructFullName(fullName);
268 StashTest(testClass, testMethod);
269 }
270
271 public void StashTest(Type testClass, MethodInfo testMethod)
272 {
273 Action<CUIComponent> test = testMethod.CreateDelegate<Action<CUIComponent>>();
274
275 ActiveTest = testMethod;
276
277 try
278 {
279 test(TestChamber);
280 TestChamber.MainComponent.Step();
281
282
283 if (!Directory.Exists(Path.Combine(TestStashPath, testClass.Name)))
284 {
285 Directory.CreateDirectory(Path.Combine(TestStashPath, testClass.Name));
286 }
287
288 string testPath = Path.Combine(TestStashPath, testClass.Name, $"{testMethod.Name}.xml");
289
290 using (StreamWriter writer = new StreamWriter(testPath, false))
291 {
292 writer.WriteLine(TestChamber.Serialize(CUIAttribute.Calculated));
293 }
294
295 OnTestReport?.Invoke(new TestReport(testClass, testMethod, TestStatus.Stashed));
296 }
297 catch (Exception e) { CUITest.Log(e, Color.Orange); }
298 }
299
300 public void PrintResultsToConsole(TestReport report)
301 {
302 string fullName = $"{report.TestClass.Name}.{report.TestMethod.Name}";
303
304 if (report.Status is TestStatus.Passed)
305 {
306 CUITest.Log($"{fullName} Passed", Color.Lime);
307 }
308
309 if (report.Status is TestStatus.Unknown)
310 {
311 CUITest.Log($"{fullName} is not stashed yet", Color.Yellow);
312 }
313
314 if (report.Status is TestStatus.Stashed)
315 {
316 CUITest.Log($"{fullName} Stashed", Color.Cyan);
317 }
318
319 if (report.Status is TestStatus.Failed)
320 {
321 CUITest.Log($"{fullName} Failed", Color.Red);
322 CUITest.Log(report.DeepCompare);
323 }
324 }
325
326
327
328 public AutomaticTestRunner()
329 {
330 GUI = new AutoTestGUI(this);
331 Config = new TestingConfig();
332 Config.OnChanged += () => GUI.UseConfig(Config);
333 Config.Load();
334
335 if (Config.Autorun != "") RunTest(Config.Autorun);
336
337 //GetClassMethodPairs().ToList().ForEach(t => CUITest.Log(t));
338 OnTestReport += PrintResultsToConsole;
339 }
340 }
341}
Base class for all components.
CUIMainComponent MainComponent
Link to CUIMainComponent, passed to children.
void Step()
Forses 1 layout update step, even when Frozen.