using System.Collections.Concurrent; using Microsoft.Extensions.Logging; using Moq; namespace UnitTests.ApplicationTests; public class MainLoopCoordinatorTests { [Fact] public async Task TestMainLoopCoordinator_InputCrashes_ExceptionSurfacesMainThread () { var mockLogger = new Mock (); var beforeLogger = Logging.Logger; Logging.Logger = mockLogger.Object; var m = new Mock> (); // Runs on a separate thread (input thread) m.Setup (f => f.CreateInput ()).Throws (new Exception ("Crash on boot")); var c = new MainLoopCoordinator (new TimedEvents (), // Rest runs on main thread new ConcurrentQueue (), Mock.Of>(), m.Object); // StartAsync boots the main loop and the input thread. But if the input class bombs // on startup it is important that the exception surface at the call site and not lost var ex = await Assert.ThrowsAsync(() => c.StartInputTaskAsync (null)); Assert.Equal ("Crash on boot", ex.InnerExceptions [0].Message); // Restore the original null logger to be polite to other tests Logging.Logger = beforeLogger; // Logs should explicitly call out that input loop crashed. mockLogger.Verify ( l => l.Log (LogLevel.Critical, It.IsAny (), It.Is ((v, t) => v.ToString ().Contains("Input loop crashed")), It.IsAny (), It.IsAny> ()) , Times.Once); } /* [Fact] public void TestMainLoopCoordinator_InputExitsImmediately_ExceptionRaisedInMainThread () { // Runs on a separate thread (input thread) // But because it's just a mock it immediately exists var mockInputFactoryMethod = () => Mock.Of> (); var mockOutput = Mock.Of (); var mockInputProcessor = Mock.Of (); var inputQueue = new ConcurrentQueue (); var timedEvents = new TimedEvents (); var mainLoop = new MainLoop (); mainLoop.Initialize (timedEvents, inputQueue, mockInputProcessor, mockOutput ); var c = new MainLoopCoordinator (timedEvents, mockInputFactoryMethod, inputQueue, mockInputProcessor, ()=>mockOutput, mainLoop ); // TODO: This test has race condition // // * When the input loop exits it can happen // * - During boot // * - After boot // * // * If it happens in boot you get input exited // * If it happens after you get "Input loop exited early (stop not called)" // // Because the console input class does not block - i.e. breaks contract // We need to let the user know input has silently exited and all has gone bad. var ex = Assert.ThrowsAsync (c.StartAsync).Result; Assert.Equal ("Input loop exited during startup instead of entering read loop properly (i.e. and blocking)", ex.Message); }*/ }