AutoInitShutdownAttribute.cs 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. using System.Diagnostics;
  2. using System.Globalization;
  3. using System.Reflection;
  4. using Xunit.Sdk;
  5. namespace UnitTests;
  6. /// <summary>
  7. /// Enables test functions annotated with the [AutoInitShutdown] attribute to
  8. /// automatically call Application.Init at start of the test and Application.Shutdown after the
  9. /// test exits.
  10. /// This is necessary because a) Application is a singleton and Init/Shutdown must be called
  11. /// as a pair, and b) all unit test functions should be atomic..
  12. /// </summary>
  13. [AttributeUsage (AttributeTargets.Class | AttributeTargets.Method)]
  14. public class AutoInitShutdownAttribute : BeforeAfterTestAttribute
  15. {
  16. /// <summary>
  17. /// Initializes a [AutoInitShutdown] attribute, which determines if/how Application.Init and Application.Shutdown
  18. /// are automatically called Before/After a test runs.
  19. /// </summary>
  20. /// <param name="autoInit">If true, Application.Init will be called Before the test runs.</param>
  21. /// <param name="consoleDriverType">
  22. /// Determines which IConsoleDriver (FakeDriver, WindowsDriver, UnixDriver, DotNetDriver)
  23. /// will be used when Application.Init is called. If null FakeDriver will be used. Only valid if
  24. /// <paramref name="autoInit"/> is true.
  25. /// </param>
  26. /// <param name="useFakeClipboard">
  27. /// If true, will force the use of <see cref="FakeDriver.FakeClipboard"/>. Only valid if
  28. /// <see cref="IConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
  29. /// </param>
  30. /// <param name="fakeClipboardAlwaysThrowsNotSupportedException">
  31. /// Only valid if <paramref name="autoInit"/> is true. Only
  32. /// valid if <see cref="IConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
  33. /// </param>
  34. /// <param name="fakeClipboardIsSupportedAlwaysTrue">
  35. /// Only valid if <paramref name="autoInit"/> is true. Only valid if
  36. /// <see cref="IConsoleDriver"/> == <see cref="FakeDriver"/> and <paramref name="autoInit"/> is true.
  37. /// </param>
  38. /// <param name="verifyShutdown">If true and <see cref="Application.Initialized"/> is true, the test will fail.</param>
  39. public AutoInitShutdownAttribute (
  40. bool autoInit = true,
  41. Type consoleDriverType = null,
  42. bool useFakeClipboard = true,
  43. bool fakeClipboardAlwaysThrowsNotSupportedException = false,
  44. bool fakeClipboardIsSupportedAlwaysTrue = false,
  45. bool verifyShutdown = false
  46. )
  47. {
  48. AutoInit = autoInit;
  49. CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US");
  50. _driverType = consoleDriverType;
  51. FakeDriver.FakeBehaviors.UseFakeClipboard = useFakeClipboard;
  52. FakeDriver.FakeBehaviors.FakeClipboardAlwaysThrowsNotSupportedException =
  53. fakeClipboardAlwaysThrowsNotSupportedException;
  54. FakeDriver.FakeBehaviors.FakeClipboardIsSupportedAlwaysFalse = fakeClipboardIsSupportedAlwaysTrue;
  55. _verifyShutdown = verifyShutdown;
  56. }
  57. private readonly bool _verifyShutdown;
  58. private readonly Type _driverType;
  59. private IDisposable _v2Cleanup;
  60. public override void After (MethodInfo methodUnderTest)
  61. {
  62. Debug.WriteLine ($"After: {methodUnderTest?.Name ?? "Unknown Test"}");
  63. // Turn off diagnostic flags in case some test left them on
  64. View.Diagnostics = ViewDiagnosticFlags.Off;
  65. _v2Cleanup?.Dispose ();
  66. if (AutoInit)
  67. {
  68. // try
  69. {
  70. if (!_verifyShutdown)
  71. {
  72. Application.ResetState (ignoreDisposed: true);
  73. }
  74. Application.Shutdown ();
  75. #if DEBUG_IDISPOSABLE
  76. if (View.Instances.Count == 0)
  77. {
  78. Assert.Empty (View.Instances);
  79. }
  80. else
  81. {
  82. View.Instances.Clear ();
  83. }
  84. #endif
  85. }
  86. //catch (Exception e)
  87. //{
  88. // Assert.Fail ($"Application.Shutdown threw an exception after the test exited: {e}");
  89. //}
  90. //finally
  91. {
  92. #if DEBUG_IDISPOSABLE
  93. View.Instances.Clear ();
  94. Application.ResetState (true);
  95. #endif
  96. }
  97. }
  98. Debug.Assert (!CM.IsEnabled, "This test left ConfigurationManager enabled!");
  99. // Force the ConfigurationManager to reset to its hardcoded defaults
  100. CM.Disable(true);
  101. }
  102. public override void Before (MethodInfo methodUnderTest)
  103. {
  104. Debug.WriteLine ($"Before: {methodUnderTest?.Name ?? "Unknown Test"}");
  105. Debug.Assert (!CM.IsEnabled, "A previous test left ConfigurationManager enabled!");
  106. // Disable & force the ConfigurationManager to reset to its hardcoded defaults
  107. CM.Disable (true);
  108. //Debug.Assert(!CM.IsEnabled, "Some other test left ConfigurationManager enabled.");
  109. if (AutoInit)
  110. {
  111. #if DEBUG_IDISPOSABLE
  112. View.EnableDebugIDisposableAsserts = true;
  113. // Clear out any lingering Responder instances from previous tests
  114. if (View.Instances.Count == 0)
  115. {
  116. Assert.Empty (View.Instances);
  117. }
  118. else
  119. {
  120. View.Instances.Clear ();
  121. }
  122. #endif
  123. if (_driverType == null)
  124. {
  125. Application.Top = null;
  126. Application.TopLevels.Clear ();
  127. var fa = new FakeApplicationFactory ();
  128. _v2Cleanup = fa.SetupFakeApplication ();
  129. AutoInitShutdownAttribute.FakeResize (new Size (80,25));
  130. }
  131. else
  132. {
  133. Application.Init ((IConsoleDriver)Activator.CreateInstance (_driverType));
  134. }
  135. }
  136. }
  137. private bool AutoInit { get; }
  138. /// <summary>
  139. /// 'Resizes' the application screen and forces layout. Only works if your test uses <see cref="AutoInitShutdownAttribute"/>
  140. /// with FakeDriver (the default).
  141. /// </summary>
  142. /// <param name="size">The new screen size.</param>
  143. /// <remarks>
  144. /// This method works with both the library FakeDriver and the fluent testing FakeConsoleDriver.
  145. /// It uses <see cref="IConsoleDriver.SetScreenSize"/> when available, or manipulates the buffer/monitor directly.
  146. /// </remarks>
  147. public static void FakeResize (Size size)
  148. {
  149. if (Application.Driver is null)
  150. {
  151. return;
  152. }
  153. // Try the library FakeDriver first - it has SetScreenSize implemented
  154. if (Application.Driver is FakeDriver fakeDriver)
  155. {
  156. fakeDriver.SetScreenSize (size.Width, size.Height);
  157. Application.LayoutAndDraw (true);
  158. return;
  159. }
  160. // For fluent testing FakeConsoleDriver (through facade), manipulate buffer and monitor directly
  161. if (Application.Driver is IConsoleDriverFacade facade)
  162. {
  163. facade.OutputBuffer.SetWindowSize (size.Width, size.Height);
  164. // Raise the size changing event through the monitor
  165. if (facade.WindowSizeMonitor is FakeSizeMonitor fakeSizeMonitor)
  166. {
  167. fakeSizeMonitor.RaiseSizeChanging (size);
  168. }
  169. else if (facade.WindowSizeMonitor is FakeWindowSizeMonitor fakeWindowSizeMonitor)
  170. {
  171. fakeWindowSizeMonitor.RaiseSizeChanging (size);
  172. }
  173. Application.LayoutAndDraw (true);
  174. return;
  175. }
  176. // Fallback: try SetScreenSize through interface (will throw if not supported)
  177. Application.Driver.SetScreenSize (size.Width, size.Height);
  178. Application.LayoutAndDraw (true);
  179. }
  180. /// <summary>
  181. /// Runs a single iteration of the main loop (layout, draw, run timed events etc.)
  182. /// </summary>
  183. public static void RunIteration ()
  184. {
  185. var a = (ApplicationImpl)ApplicationImpl.Instance;
  186. a.Coordinator?.RunIteration ();
  187. }
  188. }