using System; using System.Threading; using System.Threading.Tasks; using Implab.Components; using Xunit; namespace Implab.Test { public class RunnableComponentTests { [Fact] public async Task SimpleStartStop() { using (var m = new MockPollComponent(true)) { m.StartWorker = async (ct) => await Task.Yield(); m.StopWorker = async (ct) => await Task.Yield(); Assert.Equal(ExecutionState.Ready, m.State); Assert.NotNull(m.Completion); m.Start(); await m.Completion; Assert.Equal(ExecutionState.Running, m.State); m.Stop(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); } } [Fact] public async Task SyncStart() { using (var m = new MockPollComponent(true)) { m.Start(); Assert.Equal(ExecutionState.Running, m.State); await m.Completion; } } [Fact] public async Task AsyncStarting() { using (var m = new MockPollComponent(true)) { var signal = Safe.CreateTask(); m.StartWorker = async (ct) => await signal; m.Start(); Assert.Equal(ExecutionState.Starting, m.State); Assert.False(m.Completion.IsCompleted); signal.Start(); await m.Completion; Assert.Equal(ExecutionState.Running, m.State); } } [Fact] public async Task FailWhileStarting() { using (var m = new MockPollComponent(true)) { const string failMessage = "Fail me"; var signal = new Task(() => { throw new Exception(failMessage); }); m.StartWorker = async (ct) => await signal; m.Start(); Assert.Equal(ExecutionState.Starting, m.State); Assert.False(m.Completion.IsCompleted); signal.Start(); try { await m.Completion; Assert.True(false); } catch (Exception e) { Assert.Equal(failMessage, e.Message); } Assert.Equal(ExecutionState.Failed, m.State); } } [Fact] public async Task SyncStop() { using (var m = new MockPollComponent(true)) { m.Start(); Assert.Equal(ExecutionState.Running, m.State); m.Stop(); Assert.Equal(ExecutionState.Stopped, m.State); await m.Completion; } } [Fact] public async Task AsyncStopping() { using (var m = new MockPollComponent(true)) { var signal = Safe.CreateTask(); m.StopWorker = async (ct) => await signal; // Start m.Start(); Assert.Equal(ExecutionState.Running, m.State); // Stop m.Stop(); Assert.Equal(ExecutionState.Stopping, m.State); Assert.False(m.Completion.IsCompleted); signal.Start(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); } } [Fact] public async Task FailWhileStopping() { using (var m = new MockPollComponent(true)) { const string failMessage = "Fail me"; var signal = new Task(() => { throw new Exception(failMessage); }); m.StopWorker = async (ct) => await signal; // Start m.Start(); Assert.Equal(ExecutionState.Running, m.State); // Stop m.Stop(); Assert.Equal(ExecutionState.Stopping, m.State); Assert.False(m.Completion.IsCompleted); signal.Start(); try { await m.Completion; Assert.True(false); } catch (Exception e) { Assert.Equal(failMessage, e.Message); } Assert.Equal(ExecutionState.Failed, m.State); } } [Fact] public async Task ThrowOnInvalidTrasition() { using (var m = new MockPollComponent(false)) { var started = Safe.CreateTask(); var stopped = Safe.CreateTask(); m.StartWorker = async (ct) => await started; m.StopWorker = async (ct) => await stopped; Assert.Throws(() => m.Start()); // Initialize m.Initialize(); await m.Completion; // Start m.Start(); Assert.Equal(ExecutionState.Starting, m.State); // Check invalid transitions Assert.Throws(() => m.Start()); // Component can be stopped before started // m.Stop(CancellationToken.None); // Running started.Start(); await m.Completion; Assert.Equal(ExecutionState.Running, m.State); Assert.Throws(() => m.Start()); // Stop m.Stop(); // Check invalid transitions Assert.Throws(() => m.Start()); Assert.Throws(() => m.Stop()); // Stopped stopped.Start(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); // Check invalid transitions Assert.Throws(() => m.Start()); Assert.Throws(() => m.Stop()); } } [Fact] public async Task CancelStart() { using (var m = new MockPollComponent(true)) { m.StartWorker = (ct) => Safe.CreateTask(ct); m.Start(); var start = m.Completion; Assert.Equal(ExecutionState.Starting, m.State); m.Stop(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); Assert.True(start.IsCompleted); Assert.True(start.IsCanceled); } } [Fact] public async Task AwaitWorker() { using (var m = new MockPollComponent(true)) { var worker = Safe.CreateTask(); m.PollWorker = (ct) => worker; m.Start(CancellationToken.None); await m.Completion; Assert.Equal(ExecutionState.Running, m.State); m.Stop(CancellationToken.None); Assert.Equal(ExecutionState.Stopping, m.State); worker.Start(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); } } [Fact] public async Task CancelWorker() { using (var m = new MockPollComponent(true)) { var worker = Safe.CreateTask(); var started = Safe.CreateTask(); m.PollWorker = async (ct) => { started.Start(); await worker; ct.ThrowIfCancellationRequested(); }; m.Start(CancellationToken.None); await m.Completion; await started; // await for the poll worker to start Assert.Equal(ExecutionState.Running, m.State); m.Stop(CancellationToken.None); Assert.Equal(ExecutionState.Stopping, m.State); worker.Start(); await m.Completion; Assert.Equal(ExecutionState.Stopped, m.State); } } } }