Пример #1
0
        public async Task OverrideDueTime_DuringRun_AppliesToNextRun()
        {
            // Arrange
            const double runLength = 1.0;

            const double t1 = 1.0;
            const double tA = 1.2;
            const double t2 = 1.4;
            const double tB = 1.7;
            const double tC = 2.3;
            const double t3 = 2.5;
            const double tD = 2.8;
            const double tE = 3.8;
            const double t4 = 4.0;
            const double tF = 4.3;
            const double tG = 5.3;

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.IsEnabled = true;

            job.Schedule = new SimpleSchedule(SimpleScheduleKind.Second, 1, start);

            job.Routine = async(parameter, tracker, output, token) =>
            {
                var msg = (string)parameter;
                await output.WriteAsync(msg);

                await Task.Delay(TimeSpan.FromSeconds(runLength), token);
            };

            // Act
            job.Parameter = "Scheduled1";

            await timeMachine.WaitUntilSecondsElapse(start, tA);

            var infoA = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, t2);

            job.OverrideDueTime(start.AddSeconds(t3));
            job.Parameter = "Overridden";

            await timeMachine.WaitUntilSecondsElapse(start, tB);

            var infoB = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tC);

            var infoC = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tD);

            job.Parameter = "Scheduled2";
            var infoD = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tE);

            var infoE = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tF);

            var infoF = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tG);

            var infoG = job.GetInfo(null);

            job.Dispose();

            // Assert

            var DEFECT = TimeSpan.FromMilliseconds(30);

            #region ^A

            Assert.That(infoA.CurrentRun, Is.Not.Null);
            Assert.That(infoA.NextDueTime, Is.EqualTo(start.AddSeconds(2.0)));
            Assert.That(infoA.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoA.RunCount, Is.Zero);
            Assert.That(infoA.Runs, Is.Empty);

            var currentA = infoA.CurrentRun.Value;
            Assert.That(currentA.RunIndex, Is.EqualTo(0));
            Assert.That(currentA.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(currentA.DueTime, Is.EqualTo(start.AddSeconds(1.0)));
            Assert.That(currentA.DueTimeWasOverridden, Is.False);
            Assert.That(currentA.StartTime, Is.EqualTo(start.AddSeconds(t1)).Within(DEFECT));
            Assert.That(currentA.EndTime, Is.Null);
            Assert.That(currentA.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentA.Output, Is.EqualTo("Scheduled1"));
            Assert.That(currentA.Exception, Is.Null);

            #endregion

            #region ^B

            Assert.That(infoB.CurrentRun, Is.Not.Null);
            Assert.That(infoB.NextDueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(infoB.NextDueTimeIsOverridden, Is.True);
            Assert.That(infoB.RunCount, Is.Zero);
            Assert.That(infoB.Runs, Is.Empty);

            var currentB = infoB.CurrentRun.Value;
            Assert.That(currentA, Is.EqualTo(currentB));

            #endregion

            #region ^C

            Assert.That(infoC.CurrentRun, Is.Null);
            Assert.That(infoC.NextDueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(infoC.NextDueTimeIsOverridden, Is.True);
            Assert.That(infoC.RunCount, Is.EqualTo(1));
            Assert.That(infoC.Runs, Has.Count.EqualTo(1));

            var runC0 = infoC.Runs.Single();

            Assert.That(runC0.RunIndex, Is.EqualTo(0));
            Assert.That(runC0.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(runC0.DueTime, Is.EqualTo(start.AddSeconds(1.0)));
            Assert.That(runC0.DueTimeWasOverridden, Is.False);
            Assert.That(runC0.StartTime, Is.EqualTo(currentA.StartTime));
            Assert.That(runC0.EndTime, Is.EqualTo(runC0.StartTime.AddSeconds(runLength)).Within(DEFECT));
            Assert.That(runC0.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(runC0.Output, Is.EqualTo("Scheduled1"));
            Assert.That(runC0.Exception, Is.Null);

            #endregion

            #region ^D

            Assert.That(infoD.CurrentRun, Is.Not.Null);
            Assert.That(infoD.NextDueTime, Is.EqualTo(start.AddSeconds(3.0)));
            Assert.That(infoD.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoD.RunCount, Is.EqualTo(1));
            Assert.That(infoD.Runs, Has.Count.EqualTo(1));

            var currentD = infoD.CurrentRun.Value;
            Assert.That(currentD.RunIndex, Is.EqualTo(1));
            Assert.That(currentD.StartReason, Is.EqualTo(JobStartReason.OverriddenDueTime));
            Assert.That(currentD.DueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(currentD.DueTimeWasOverridden, Is.True);
            Assert.That(currentD.StartTime, Is.EqualTo(start.AddSeconds(t3)).Within(DEFECT));
            Assert.That(currentD.EndTime, Is.Null);
            Assert.That(currentD.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentD.Output, Is.EqualTo("Overridden"));
            Assert.That(currentD.Exception, Is.Null);

            Assert.That(infoD.Runs.Single(), Is.EqualTo(runC0));

            #endregion

            #region ^E

            Assert.That(infoE.CurrentRun, Is.Null);
            Assert.That(infoE.NextDueTime, Is.EqualTo(start.AddSeconds(t4)));
            Assert.That(infoE.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoE.RunCount, Is.EqualTo(2));
            Assert.That(infoE.Runs, Has.Count.EqualTo(2));

            Assert.That(infoE.Runs[0], Is.EqualTo(infoD.Runs[0]));

            var runE1 = infoE.Runs[1];

            Assert.That(runE1.RunIndex, Is.EqualTo(1));
            Assert.That(runE1.StartReason, Is.EqualTo(JobStartReason.OverriddenDueTime));
            Assert.That(runE1.DueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(runE1.DueTimeWasOverridden, Is.True);
            Assert.That(runE1.StartTime, Is.EqualTo(currentD.StartTime));
            Assert.That(runE1.EndTime, Is.EqualTo(runE1.StartTime.AddSeconds(runLength)).Within(DEFECT));
            Assert.That(runE1.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(runE1.Output, Is.EqualTo("Overridden"));
            Assert.That(runE1.Exception, Is.Null);

            #endregion

            #region ^F

            Assert.That(infoF.CurrentRun, Is.Not.Null);
            Assert.That(infoF.NextDueTime, Is.EqualTo(start.AddSeconds(5.0)));
            Assert.That(infoF.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoF.RunCount, Is.EqualTo(2));
            Assert.That(infoF.Runs, Has.Count.EqualTo(2));

            var currentF = infoF.CurrentRun.Value;
            Assert.That(currentF.RunIndex, Is.EqualTo(2));
            Assert.That(currentF.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(currentF.DueTime, Is.EqualTo(start.AddSeconds(t4)));
            Assert.That(currentF.DueTimeWasOverridden, Is.False);
            Assert.That(currentF.StartTime, Is.EqualTo(start.AddSeconds(t4)).Within(DEFECT));
            Assert.That(currentF.EndTime, Is.Null);
            Assert.That(currentF.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentF.Output, Is.EqualTo("Scheduled2"));
            Assert.That(currentF.Exception, Is.Null);

            Assert.That(infoF.Runs[0], Is.EqualTo(infoE.Runs[0]));
            Assert.That(infoF.Runs[1], Is.EqualTo(infoE.Runs[1]));

            #endregion

            #region ^G

            Assert.That(infoG.CurrentRun, Is.Null);
            Assert.That(infoG.NextDueTime, Is.EqualTo(start.AddSeconds(6.0)));
            Assert.That(infoG.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoG.RunCount, Is.EqualTo(3));
            Assert.That(infoG.Runs, Has.Count.EqualTo(3));

            Assert.That(infoG.Runs[0], Is.EqualTo(infoF.Runs[0]));
            Assert.That(infoG.Runs[1], Is.EqualTo(infoF.Runs[1]));

            var runG2 = infoG.Runs[2];

            Assert.That(runG2.RunIndex, Is.EqualTo(2));
            Assert.That(runG2.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(runG2.DueTime, Is.EqualTo(start.AddSeconds(t4)));
            Assert.That(runG2.DueTimeWasOverridden, Is.False);
            Assert.That(runG2.StartTime, Is.EqualTo(currentF.StartTime));
            Assert.That(runG2.EndTime, Is.EqualTo(runG2.StartTime.AddSeconds(runLength)).Within(DEFECT));
            Assert.That(runG2.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(runG2.Output, Is.EqualTo("Scheduled2"));
            Assert.That(runG2.Exception, Is.Null);

            #endregion
        }
Пример #2
0
        public async Task OverrideDueTime_Null_DefaultsToSchedule()
        {
            // Arrange
            const double runLength = 0.7;
            const double t1        = 1.5;
            const double t2        = 1.9;
            const double tA        = 2.2;
            const double t3        = 2.5;
            const double tB        = 3.3;

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.IsEnabled = true;

            job.Routine = async(parameter, tracker, output, token) =>
            {
                var msg = (string)parameter;
                await output.WriteAsync(msg);

                await Task.Delay(TimeSpan.FromSeconds(runLength), token);
            };

            // Act
            await timeMachine.WaitUntilSecondsElapse(start, t1);

            job.OverrideDueTime(start.AddSeconds(t3));

            job.Parameter = "Hello!";

            await timeMachine.WaitUntilSecondsElapse(start, t2);

            job.OverrideDueTime(null);

            await timeMachine.WaitUntilSecondsElapse(start, tA);

            var infoA = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tB);

            var infoB = job.GetInfo(null);

            // Assert

            #region ^A

            Assert.That(infoA.CurrentRun, Is.Null);
            Assert.That(infoA.NextDueTime, Is.EqualTo(TestHelper.NeverCopy));
            Assert.That(infoA.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoA.RunCount, Is.Zero);
            Assert.That(infoA.Runs, Is.Empty);

            #endregion

            #region ^B

            Assert.That(infoB.CurrentRun, Is.Null);
            Assert.That(infoB.NextDueTime, Is.EqualTo(TestHelper.NeverCopy));
            Assert.That(infoB.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoB.RunCount, Is.Zero);
            Assert.That(infoB.Runs, Is.Empty);

            #endregion
        }
Пример #3
0
        public async Task OverrideDueTime_NotNull_StartsAndDiscards()
        {
            // Arrange
            const double runLength = 0.7;
            const double t1        = 1.7;
            const double tA        = 2.1;
            const double t2        = 2.5;
            const double tB        = 3.1;
            const double tC        = 4.0;

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.IsEnabled = true;

            job.Routine = async(parameter, tracker, output, token) =>
            {
                var msg = (string)parameter;
                await output.WriteAsync(msg);

                await Task.Delay(TimeSpan.FromSeconds(runLength), token);
            };

            // Act
            await timeMachine.WaitUntilSecondsElapse(start, t1);

            job.OverrideDueTime(start.AddSeconds(t2));

            await timeMachine.WaitUntilSecondsElapse(start, tA);

            var infoA = job.GetInfo(null);

            job.Parameter = "Hello!";

            await timeMachine.WaitUntilSecondsElapse(start, tB);

            var infoB = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tC);

            var infoC = job.GetInfo(null);

            // Assert
            var DEFECT = TimeSpan.FromMilliseconds(30);

            #region ^A

            Assert.That(infoA.CurrentRun, Is.Null);
            Assert.That(infoA.NextDueTime, Is.EqualTo(start.AddSeconds(t2)));
            Assert.That(infoA.NextDueTimeIsOverridden, Is.True);
            Assert.That(infoA.RunCount, Is.Zero);
            Assert.That(infoA.Runs, Is.Empty);

            #endregion

            #region ^B

            Assert.That(infoB.CurrentRun, Is.Not.Null);
            Assert.That(infoB.NextDueTime, Is.EqualTo(TestHelper.NeverCopy));
            Assert.That(infoB.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoB.RunCount, Is.Zero);
            Assert.That(infoB.Runs, Is.Empty);

            var currentB = infoB.CurrentRun.Value;
            Assert.That(currentB.RunIndex, Is.EqualTo(0));
            Assert.That(currentB.StartReason, Is.EqualTo(JobStartReason.OverriddenDueTime));
            Assert.That(currentB.DueTime, Is.EqualTo(start.AddSeconds(t2)));
            Assert.That(currentB.DueTimeWasOverridden, Is.True);
            Assert.That(currentB.StartTime, Is.EqualTo(start.AddSeconds(t2)).Within(DEFECT));
            Assert.That(currentB.EndTime, Is.Null);
            Assert.That(currentB.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentB.Output, Is.EqualTo("Hello!"));
            Assert.That(currentB.Exception, Is.Null);

            #endregion

            #region ^C

            Assert.That(infoC.CurrentRun, Is.Null);
            Assert.That(infoC.NextDueTime, Is.EqualTo(TestHelper.NeverCopy));
            Assert.That(infoC.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoC.RunCount, Is.EqualTo(1));
            Assert.That(infoC.Runs, Has.Count.EqualTo(1));

            var infoCRun0 = infoC.Runs[0];
            Assert.That(infoCRun0.RunIndex, Is.EqualTo(0));
            Assert.That(infoCRun0.StartReason, Is.EqualTo(JobStartReason.OverriddenDueTime));
            Assert.That(infoCRun0.DueTime, Is.EqualTo(start.AddSeconds(t2)));
            Assert.That(infoCRun0.DueTimeWasOverridden, Is.True);
            Assert.That(infoCRun0.StartTime, Is.EqualTo(currentB.StartTime));
            Assert.That(infoCRun0.EndTime, Is.EqualTo(infoCRun0.StartTime.AddSeconds(runLength)).Within(DEFECT * 2));
            Assert.That(infoCRun0.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(infoCRun0.Output, Is.EqualTo("Hello!"));
            Assert.That(infoCRun0.Exception, Is.Null);

            #endregion
        }
Пример #4
0
        public async Task Schedule_SetDuringRun_DoesNotAffectRunButAppliesToDueTime()
        {
            // Arrange
            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job       = jobManager.Create("my-job");
            var schedule1 = new SimpleSchedule(SimpleScheduleKind.Second, 1, start);

            job.Schedule = schedule1; // job will be started by due time of this schedule

            DateTimeOffset dueTime1 = default;
            DateTimeOffset dueTime2 = default;
            DateTimeOffset dueTime3 = default;
            DateTimeOffset dueTime4 = default;

            job.Routine = async(parameter, tracker, output, token) =>
            {
                Log.Debug("Entered routine.");

                // start + 1.2s: due time is (start + 2s), set by schedule1
                await timeMachine.WaitUntilSecondsElapse(start, 1.2, token);

                dueTime1 = job.GetInfo(0).NextDueTime; // should be 2s

                await timeMachine.WaitUntilSecondsElapse(start, 1.7, token);

                dueTime2 = job.GetInfo(0).NextDueTime;

                await timeMachine.WaitUntilSecondsElapse(start, 2.2, token);

                dueTime3 = job.GetInfo(0).NextDueTime;

                await timeMachine.WaitUntilSecondsElapse(start, 3.2, token);

                dueTime4 = job.GetInfo(0).NextDueTime;

                await Task.Delay(TimeSpan.FromHours(2), token);

                Log.Debug("Exited routine.");
            };

            job.IsEnabled = true;

            // Act
            var schedule2 = new SimpleSchedule(SimpleScheduleKind.Second, 1, start.AddSeconds(1.8));
            await timeMachine.WaitUntilSecondsElapse(start, 1.4);

            job.Schedule = schedule2;

            await timeMachine.WaitUntilSecondsElapse(start, 4);

            // Assert
            try
            {
                Assert.That(dueTime1, Is.EqualTo(start.AddSeconds(2)));
                Assert.That(dueTime2, Is.EqualTo(start.AddSeconds(1.8)));
                Assert.That(dueTime3, Is.EqualTo(start.AddSeconds(2.8)));
                Assert.That(dueTime4, Is.EqualTo(start.AddSeconds(3.8)));
            }
            catch (Exception ex)
            {
                var sb = new StringBuilder();
                sb.AppendLine("*** Test Failed ***");
                sb.AppendLine(ex.ToString());
                sb.AppendLine("*** Log: ***");

                var log = _logWriter.ToString();

                sb.AppendLine(log);

                Assert.Fail(sb.ToString());
            }
        }
Пример #5
0
        public async Task Dispose_JobsCreated_DisposesAndJobsAreCanceledAndDisposed()
        {
            // Arrange
            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start = "2020-01-01Z".ToUtcDateOffset();

            TimeProvider.Override(ShiftedTimeProvider.CreateTimeMachine(start));


            var job1 = jobManager.Create("job1");

            job1.IsEnabled = true;

            var job2 = jobManager.Create("job2");

            job2.IsEnabled = true;

            job1.Output = new StringWriterWithEncoding(Encoding.UTF8);
            job2.Output = new StringWriterWithEncoding(Encoding.UTF8);

            async Task Routine(object parameter, IProgressTracker tracker, TextWriter output, CancellationToken token)
            {
                for (var i = 0; i < 100; i++)
                {
                    var time = TimeProvider.GetCurrentTime();
                    await output.WriteLineAsync($"Iteration {i}: {time.Second:D2}:{time.Millisecond:D3}");

                    try
                    {
                        await Task.Delay(1000, token);
                    }
                    catch (TaskCanceledException)
                    {
                        time = TimeProvider.GetCurrentTime();
                        await output.WriteLineAsync($"Canceled! {time.Second:D2}:{time.Millisecond:D3}");

                        throw;
                    }
                }
            }

            ISchedule schedule = new SimpleSchedule(
                SimpleScheduleKind.Second,
                1,
                start.AddMilliseconds(400));

            job1.Schedule = schedule;
            job2.Schedule = schedule;

            job1.Routine = Routine;
            job2.Routine = Routine;

            job1.IsEnabled = true;
            job2.IsEnabled = true;

            await Task.Delay(2500); // 3 iterations should be completed: ~400, ~1400, ~2400 todo: ut this

            // Act
            var jobInfoBeforeDispose1 = job1.GetInfo(null);
            var jobInfoBeforeDispose2 = job2.GetInfo(null);

            jobManager.Dispose();
            await Task.Delay(50); // let background TPL work get done.

            // Assert
            Assert.That(jobManager.IsDisposed, Is.True);

            foreach (var job in new[] { job1, job2 })
            {
                Assert.That(job.IsDisposed, Is.True);
                var info = job.GetInfo(null);
                var run  = info.Runs.Single();
                Assert.That(run.Status, Is.EqualTo(JobRunStatus.Canceled));
            }
        }
Пример #6
0
        public async Task Schedule_SetAndStartedAndCompleted_ReflectedInOldRuns()
        {
            // Arrange
            var DEFECT = TimeSpan.FromMilliseconds(30);

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.Routine = async(parameter, tracker, output, token) =>
            {
                await Task.Delay(1500, token); // 1.5 second to complete
            };
            ISchedule schedule = new SimpleSchedule(SimpleScheduleKind.Second, 1, start);

            job.IsEnabled = true;

            // Act
            job.Schedule = schedule; // will fire at 00:01

            await timeMachine.WaitUntilSecondsElapse(
                start,
                1.0 + DEFECT.TotalSeconds + 1.5 + DEFECT.TotalSeconds);

            // Assert
            try
            {
                var info = job.GetInfo(null);
                Assert.That(info.CurrentRun, Is.Null);

                Assert.That(info.NextDueTime, Is.EqualTo(start.AddSeconds(3)));

                var pastRun = info.Runs.Single();

                Assert.That(pastRun.RunIndex, Is.EqualTo(0));
                Assert.That(pastRun.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
                Assert.That(pastRun.DueTime, Is.EqualTo(start.AddSeconds(1)));
                Assert.That(pastRun.DueTimeWasOverridden, Is.False);

                Assert.That(pastRun.StartTime, Is.EqualTo(start.AddSeconds(1)).Within(DEFECT));
                Assert.That(
                    pastRun.EndTime,
                    Is.EqualTo(pastRun.StartTime.AddSeconds(1.5)).Within(DEFECT));

                Assert.That(pastRun.Status, Is.EqualTo(JobRunStatus.Completed));
            }
            catch (Exception ex)
            {
                // todo: need this block, here & in other places?
                // it is known now that the reason was slowpok TPL

                var sb = new StringBuilder();
                sb.AppendLine("*** Test Failed ***");
                sb.AppendLine(ex.ToString());
                sb.AppendLine("*** Log: ***");

                var log = _logWriter.ToString();

                sb.AppendLine(log);

                Assert.Fail(sb.ToString());
            }
        }
Пример #7
0
        public async Task Schedule_SetAndStartedAndFaulted_ReflectedInOldRuns()
        {
            // Arrange
            var DEFECT = TimeSpan.FromMilliseconds(30);

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.Routine = async(parameter, tracker, output, token) =>
            {
                await Task.Delay(1500, token);              // 1.5 second runs with no problem...

                throw new ApplicationException("BAD_NEWS"); // ...and then throws!
            };
            ISchedule schedule = new SimpleSchedule(SimpleScheduleKind.Second, 1, start);

            job.IsEnabled = true;

            // Act
            job.Schedule = schedule; // will fire at 00:01

            await timeMachine.WaitUntilSecondsElapse(start,
                                                     2.8); // by this time, job will end due to the exception "BAD_NEWS" and finalize.

            // Assert
            var info = job.GetInfo(null);

            Assert.That(info.CurrentRun, Is.Null);



            Assert.That(info.NextDueTime, Is.EqualTo(start.AddSeconds(3)));
            // todo
            //Expected: 2000 - 01 - 01 00:00:03 + 00:00
            //But was:  2000 - 01 - 01 00:00:01 + 00:00



            Assert.That(info.NextDueTimeIsOverridden, Is.False);

            var pastRun = info.Runs.Single();

            Assert.That(pastRun.RunIndex, Is.EqualTo(0));
            Assert.That(pastRun.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(pastRun.DueTime, Is.EqualTo(start.AddSeconds(1)));
            Assert.That(pastRun.DueTimeWasOverridden, Is.False);

            Assert.That(pastRun.StartTime, Is.EqualTo(start.AddSeconds(1)).Within(DEFECT));
            Assert.That(
                pastRun.EndTime,
                Is.EqualTo(pastRun.StartTime.AddSeconds(1.5)).Within(DEFECT * 2));

            Assert.That(pastRun.Status, Is.EqualTo(JobRunStatus.Faulted));
            Assert.That(pastRun.Exception, Is.TypeOf <ApplicationException>());
            Assert.That(pastRun.Exception, Has.Message.EqualTo("BAD_NEWS"));
        }
Пример #8
0
        public async Task Parameter_SetOnTheFly_RunsWithOldParameterAndNextTimeRunsWithNewParameter()
        {
            // Arrange
            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            ISchedule schedule = new ConcreteSchedule(
                start.AddSeconds(1),
                start.AddSeconds(3));

            job.Schedule = schedule;
            job.Output   = new StringWriterWithEncoding(Encoding.UTF8);

            object parameter1 = "Olia";
            object parameter2 = "Ira";

            job.Routine = async(parameter, tracker, writer, token) =>
            {
                await Task.Delay(1500, token);

                await writer.WriteAsync($"Hello, {parameter}!");
            };

            job.IsEnabled = true;

            // Act
            await timeMachine.WaitUntilSecondsElapse(start, 0.8);

            job.Parameter = parameter1;

            await timeMachine.WaitUntilSecondsElapse(start, 1.3);

            job.Parameter = parameter2;

            await timeMachine.WaitUntilSecondsElapse(start, 2.8);

            var output0 = job.Output.ToString();

            await timeMachine.WaitUntilSecondsElapse(start, 4.8);

            var output1 = job.Output.ToString();

            var info = job.GetInfo(null);

            // Assert
            Assert.That(info.CurrentRun, Is.Null);
            Assert.That(info.RunCount, Is.EqualTo(2));
            Assert.That(info.Runs, Has.Count.EqualTo(2));

            var run0 = info.Runs[0];
            var run1 = info.Runs[1];

            Assert.That(run0.Output, Is.EqualTo("Hello, Olia!"));
            Assert.That(output0, Is.EqualTo("Hello, Olia!"));

            Assert.That(run1.Output, Is.EqualTo("Hello, Ira!"));
            Assert.That(output1, Is.EqualTo("Hello, Olia!Hello, Ira!"));
        }
Пример #9
0
        public async Task GetInfo_RunsSeveralTimes_ReturnsAllRuns()
        {
            // Arrange
            const double runTime = 0.7;

            const double t1 = 1.6; // !1 - force start
            const double t2 = 2.8; // !2 - due time is overridden
            const double t3 = 3.4; // !3 - overridden due time
            const double t4 = 5.0; // !4 - starts by schedule

            const double tA = 1.1; // ^A - before all runs
            const double tB = 1.9; // ^B - right after force start
            const double tC = 2.6; // ^C - after forced run completes, but before time override
            const double tD = 3.1; // ^D - after time override, but before overridden-due-time start
            const double tE = 3.9; // ^E - after overridden-due-time start
            const double tF = 4.8; // ^F - after overridden-due-time run completes, and before schedule-due-time start
            const double tG = 5.3; // ^G - after schedule-due-time start
            const double tH = 6.6; // ^H - after schedule-due-time run completes

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.IsEnabled = true;

            job.Routine = async(parameter, tracker, output, token) =>
            {
                var msg = (string)parameter;
                await output.WriteAsync(msg);

                await Task.Delay(TimeSpan.FromSeconds(runTime), token);
            };

            ISchedule schedule = new ConcreteSchedule(
                start.AddSeconds(2),
                start.AddSeconds(3),
                start.AddSeconds(4),
                start.AddSeconds(5));

            job.Schedule = schedule;

            // Act

            // ^A - before all runs
            await timeMachine.WaitUntilSecondsElapse(start, tA);

            var infoA = job.GetInfo(null);

            // !1 - force start
            await timeMachine.WaitUntilSecondsElapse(start, t1);

            job.Parameter = "force";
            job.ForceStart();

            // ^B - right after force start
            await timeMachine.WaitUntilSecondsElapse(start, tB);

            var infoB = job.GetInfo(null);

            // ^C - after forced run completes, but before time override
            await timeMachine.WaitUntilSecondsElapse(start, tC);

            var infoC = job.GetInfo(null);

            // !2 - due time is overridden
            await timeMachine.WaitUntilSecondsElapse(start, t2);

            job.Parameter = "overridden";
            job.OverrideDueTime(start.AddSeconds(t3));

            // ^D - after time override, but before overridden-due-time start
            await timeMachine.WaitUntilSecondsElapse(start, tD);

            var infoD = job.GetInfo(null);

            // !3 - overridden due time
            await timeMachine.WaitUntilSecondsElapse(start, t3);

            await _logWriter.WriteLineAsync($"=== t3 came! {t3} ===");

            // ^E - after overridden-due-time start
            await timeMachine.WaitUntilSecondsElapse(start, tE);

            var infoE = job.GetInfo(null);

            // ^F - after overridden-due-time run completes, and before schedule-due-time start
            await timeMachine.WaitUntilSecondsElapse(start, tF);

            var infoF = job.GetInfo(null);

            job.Parameter = "schedule";

            // !4 - starts by schedule
            await timeMachine.WaitUntilSecondsElapse(start, t4);

            await _logWriter.WriteLineAsync($"=== t4 came! {t4} ===");

            // ^G - after schedule-due-time start
            await timeMachine.WaitUntilSecondsElapse(start, tG);

            var infoG = job.GetInfo(null);

            // ^H - after schedule-due-time run completes
            await timeMachine.WaitUntilSecondsElapse(start, tH);

            var infoH = job.GetInfo(null);

            // dispose
            jobManager.Dispose();
            var infoFinal = job.GetInfo(null);

            // Assert
            var DEFECT = TimeSpan.FromMilliseconds(30);


            #region ^A - before all runs

            Assert.That(infoA.CurrentRun, Is.Null);
            Assert.That(infoA.NextDueTime, Is.EqualTo(start.AddSeconds(2)));
            Assert.That(infoA.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoA.RunCount, Is.Zero);
            Assert.That(infoA.Runs, Is.Empty);

            #endregion

            #region ^B - right after force start

            Assert.That(infoB.CurrentRun, Is.Not.Null);
            Assert.That(infoB.NextDueTime, Is.EqualTo(start.AddSeconds(2)));
            Assert.That(infoB.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoB.RunCount, Is.Zero);
            Assert.That(infoB.Runs, Is.Empty);

            var currentB = infoB.CurrentRun.Value;
            Assert.That(currentB.RunIndex, Is.EqualTo(0));
            Assert.That(currentB.StartReason, Is.EqualTo(JobStartReason.Force));
            Assert.That(currentB.DueTime, Is.EqualTo(start.AddSeconds(2)));
            Assert.That(currentB.DueTimeWasOverridden, Is.False);
            Assert.That(currentB.StartTime, Is.EqualTo(start.AddSeconds(t1)).Within(DEFECT));
            Assert.That(currentB.EndTime, Is.Null);
            Assert.That(currentB.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentB.Output, Is.EqualTo("force"));
            Assert.That(currentB.Exception, Is.Null);

            #endregion

            #region ^C - after forced run completes, but before time override

            Assert.That(infoC.CurrentRun, Is.Null);
            Assert.That(infoC.NextDueTime, Is.EqualTo(start.AddSeconds(3)));
            Assert.That(infoC.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoC.RunCount, Is.EqualTo(1));
            Assert.That(infoC.Runs, Has.Count.EqualTo(1));

            var infoCRun0 = infoC.Runs[0];
            Assert.That(infoCRun0.RunIndex, Is.EqualTo(0));
            Assert.That(infoCRun0.StartReason, Is.EqualTo(JobStartReason.Force));
            Assert.That(infoCRun0.DueTime, Is.EqualTo(start.AddSeconds(2)));
            Assert.That(infoCRun0.DueTimeWasOverridden, Is.False);
            Assert.That(infoCRun0.StartTime, Is.EqualTo(currentB.StartTime));
            Assert.That(infoCRun0.EndTime, Is.EqualTo(infoCRun0.StartTime.AddSeconds(runTime)).Within(DEFECT * 2));
            Assert.That(infoCRun0.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(infoCRun0.Output, Is.EqualTo("force"));
            Assert.That(infoCRun0.Exception, Is.Null);

            #endregion

            #region ^D - after time override, but before overridden-due-time start

            Assert.That(infoD.CurrentRun, Is.Null);
            Assert.That(infoD.NextDueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(infoD.NextDueTimeIsOverridden, Is.True);
            Assert.That(infoD.RunCount, Is.EqualTo(1));
            Assert.That(infoD.Runs, Has.Count.EqualTo(1));

            Assert.That(infoD.Runs[0], Is.EqualTo(infoC.Runs[0]));

            #endregion

            #region ^E - after overridden-due-time start

            Assert.That(infoE.CurrentRun, Is.Not.Null);
            Assert.That(infoE.NextDueTime, Is.EqualTo(start.AddSeconds(4)));
            Assert.That(infoE.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoE.RunCount, Is.EqualTo(1));
            Assert.That(infoE.Runs, Has.Count.EqualTo(1));

            var currentE = infoE.CurrentRun.Value;
            Assert.That(currentE.RunIndex, Is.EqualTo(1));
            Assert.That(currentE.StartReason, Is.EqualTo(JobStartReason.OverriddenDueTime));
            Assert.That(currentE.DueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(currentE.DueTimeWasOverridden, Is.True);
            Assert.That(currentE.StartTime, Is.EqualTo(start.AddSeconds(t3)).Within(DEFECT));
            Assert.That(currentE.EndTime, Is.Null);
            Assert.That(currentE.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentE.Output, Is.EqualTo("overridden"));
            Assert.That(currentE.Exception, Is.Null);


            #endregion

            #region ^F - after overridden-due-time run completes, and before schedule-due-time start

            Assert.That(infoF.CurrentRun, Is.Null);
            Assert.That(infoF.NextDueTime, Is.EqualTo(start.AddSeconds(5)));
            Assert.That(infoF.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoF.RunCount, Is.EqualTo(2));
            Assert.That(infoF.Runs, Has.Count.EqualTo(2));

            var infoFRun0 = infoF.Runs[0];
            var infoFRun1 = infoF.Runs[1];

            Assert.That(infoFRun0, Is.EqualTo(infoD.Runs[0]));

            Assert.That(infoFRun1.RunIndex, Is.EqualTo(1));
            Assert.That(infoFRun1.StartReason, Is.EqualTo(JobStartReason.OverriddenDueTime));
            Assert.That(infoFRun1.DueTime, Is.EqualTo(start.AddSeconds(t3)));
            Assert.That(infoFRun1.DueTimeWasOverridden, Is.True);
            Assert.That(infoFRun1.StartTime, Is.EqualTo(currentE.StartTime));
            Assert.That(infoFRun1.EndTime, Is.EqualTo(infoFRun1.StartTime.AddSeconds(runTime)).Within(DEFECT * 2));
            Assert.That(infoFRun1.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(infoFRun1.Output, Is.EqualTo("overridden"));
            Assert.That(infoFRun1.Exception, Is.Null);


            #endregion

            #region ^G - after schedule-due-time start

            Assert.That(infoG.CurrentRun, Is.Not.Null);
            Assert.That(infoG.NextDueTime, Is.EqualTo(TestHelper.NeverCopy));
            Assert.That(infoG.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoG.RunCount, Is.EqualTo(2));
            Assert.That(infoG.Runs, Has.Count.EqualTo(2));

            var currentG = infoG.CurrentRun.Value;
            Assert.That(currentG.RunIndex, Is.EqualTo(2));
            Assert.That(currentG.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(currentG.DueTime, Is.EqualTo(start.AddSeconds(5)));
            Assert.That(currentG.DueTimeWasOverridden, Is.False);
            Assert.That(currentG.StartTime, Is.EqualTo(start.AddSeconds(5)).Within(DEFECT));
            Assert.That(currentG.EndTime, Is.Null);
            Assert.That(currentG.Status, Is.EqualTo(JobRunStatus.Running));
            Assert.That(currentG.Output, Is.EqualTo("schedule"));
            Assert.That(currentG.Exception, Is.Null);

            CollectionAssert.AreEqual(infoF.Runs, infoG.Runs);

            #endregion

            #region ^H - after schedule-due-time run completes

            Assert.That(infoH.CurrentRun, Is.Null);
            Assert.That(infoH.NextDueTime, Is.EqualTo(TestHelper.NeverCopy));
            Assert.That(infoH.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoH.RunCount, Is.EqualTo(3));
            Assert.That(infoH.Runs, Has.Count.EqualTo(3));

            CollectionAssert.AreEqual(infoG.Runs.Take(2), infoH.Runs.Take(2));

            var infoHRun2 = infoH.Runs[2];

            Assert.That(infoHRun2.RunIndex, Is.EqualTo(2));
            Assert.That(infoHRun2.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(infoHRun2.DueTime, Is.EqualTo(start.AddSeconds(5)));
            Assert.That(infoHRun2.DueTimeWasOverridden, Is.False);
            Assert.That(infoHRun2.StartTime, Is.EqualTo(currentG.StartTime));
            Assert.That(infoHRun2.EndTime, Is.EqualTo(infoHRun2.StartTime.AddSeconds(runTime)).Within(DEFECT * 2));
            Assert.That(infoHRun2.Status, Is.EqualTo(JobRunStatus.Completed));
            Assert.That(infoHRun2.Output, Is.EqualTo("schedule"));
            Assert.That(infoHRun2.Exception, Is.Null);

            #endregion

            #region after disposal

            Assert.That(infoFinal.CurrentRun, Is.EqualTo(infoH.CurrentRun));
            Assert.That(infoFinal.NextDueTime, Is.EqualTo(infoH.NextDueTime));
            Assert.That(infoFinal.NextDueTimeIsOverridden, Is.EqualTo(infoH.NextDueTimeIsOverridden));
            Assert.That(infoFinal.RunCount, Is.EqualTo(infoH.RunCount));
            Assert.That(infoFinal.Runs.Count, Is.EqualTo(infoH.Runs.Count));

            CollectionAssert.AreEqual(infoH.Runs, infoFinal.Runs);

            #endregion
        }
Пример #10
0
        public async Task IsEnabled_SetToFalseDuringRun_RunCompletesThenDoesNotStart()
        {
            // Arrange
            const double runLength = 1.0;

            const double t1 = 1.0;
            const double t2 = 1.3;
            const double tA = 1.7;
            const double tB = 4.5;

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.IsEnabled = true;

            job.Schedule = new SimpleSchedule(SimpleScheduleKind.Second, 1, start);

            job.Routine = async(parameter, tracker, output, token) =>
            {
                await output.WriteAsync("Hello!");

                await Task.Delay(TimeSpan.FromSeconds(runLength), token);
            };

            // Act
            await timeMachine.WaitUntilSecondsElapse(start, t2);

            job.IsEnabled = false;

            await timeMachine.WaitUntilSecondsElapse(start, tA);

            var infoA = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tB);

            var infoB = job.GetInfo(null);

            job.Dispose();

            // Assert

            var DEFECT = TimeSpan.FromMilliseconds(30);

            #region ^A

            Assert.That(infoA.CurrentRun, Is.Not.Null);
            Assert.That(infoA.NextDueTime, Is.EqualTo(start.AddSeconds(2.0)));
            Assert.That(infoA.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoA.RunCount, Is.Zero);
            Assert.That(infoA.Runs, Is.Empty);

            var currentA = infoA.CurrentRun.Value;
            Assert.That(currentA.RunIndex, Is.EqualTo(0));
            Assert.That(currentA.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(currentA.DueTime, Is.EqualTo(start.AddSeconds(t1)));
            Assert.That(currentA.DueTimeWasOverridden, Is.False);
            Assert.That(currentA.StartTime, Is.EqualTo(start.AddSeconds(t1)).Within(DEFECT));
            Assert.That(currentA.EndTime, Is.Null);
            Assert.That(currentA.Status, Is.EqualTo(JobRunStatus.Running));

            #endregion

            #region ^B

            Assert.That(infoB.CurrentRun, Is.Null);
            Assert.That(infoB.NextDueTime, Is.EqualTo(start.AddSeconds(5.0)));
            Assert.That(infoB.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoB.RunCount, Is.EqualTo(1));
            Assert.That(infoB.Runs, Has.Count.EqualTo(1));

            var runB0 = infoB.Runs.Single();
            Assert.That(runB0.RunIndex, Is.EqualTo(0));
            Assert.That(runB0.StartReason, Is.EqualTo(JobStartReason.ScheduleDueTime));
            Assert.That(runB0.DueTime, Is.EqualTo(start.AddSeconds(t1)));
            Assert.That(runB0.DueTimeWasOverridden, Is.False);
            Assert.That(runB0.StartTime, Is.EqualTo(currentA.StartTime));
            Assert.That(runB0.EndTime, Is.EqualTo(runB0.StartTime.AddSeconds(runLength)).Within(DEFECT));
            Assert.That(runB0.Status, Is.EqualTo(JobRunStatus.Completed));

            #endregion
        }
Пример #11
0
        public async Task IsEnabled_SetToFalseBeforeOverriddenDueTime_DoesNotStartThenOverriddenDueTimeGetsDiscarded()
        {
            // Arrange
            const double runLength = 1.0;

            const double t1 = 0.8;
            const double t2 = 1.1;
            const double t3 = 1.5;
            const double tA = 1.9;
            const double tB = 4.5;

            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            job.IsEnabled = true;

            job.Schedule = new SimpleSchedule(SimpleScheduleKind.Second, 1, start);

            job.Routine = async(parameter, tracker, output, token) =>
            {
                await output.WriteAsync("Hello!");

                await Task.Delay(TimeSpan.FromSeconds(runLength), token);
            };

            // Act
            await timeMachine.WaitUntilSecondsElapse(start, t1);

            job.OverrideDueTime(start.AddSeconds(t3));

            await timeMachine.WaitUntilSecondsElapse(start, t2);

            job.IsEnabled = false;

            await timeMachine.WaitUntilSecondsElapse(start, tA);

            var infoA = job.GetInfo(null);

            await timeMachine.WaitUntilSecondsElapse(start, tB);

            var infoB = job.GetInfo(null);

            job.Dispose();

            // Assert

            #region ^A

            Assert.That(infoA.CurrentRun, Is.Null);
            Assert.That(infoA.NextDueTime, Is.EqualTo(start.AddSeconds(2.0)));
            Assert.That(infoA.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoA.RunCount, Is.Zero);
            Assert.That(infoA.Runs, Is.Empty);

            #endregion

            #region ^B

            Assert.That(infoB.CurrentRun, Is.Null);
            Assert.That(infoB.NextDueTime, Is.EqualTo(start.AddSeconds(5.0)));
            Assert.That(infoB.NextDueTimeIsOverridden, Is.False);
            Assert.That(infoB.RunCount, Is.Zero);
            Assert.That(infoB.Runs, Is.Empty);

            #endregion
        }
Пример #12
0
        public async Task Output_SetOnTheFly_RunsWithOldParameterAndNextTimeRunsWithNewOutput()
        {
            // Arrange
            using IJobManager jobManager = TestHelper.CreateJobManager(true);

            var start       = "2000-01-01Z".ToUtcDateOffset();
            var timeMachine = ShiftedTimeProvider.CreateTimeMachine(start);

            TimeProvider.Override(timeMachine);

            var job = jobManager.Create("my-job");

            ISchedule schedule = new ConcreteSchedule(
                start.AddSeconds(1),
                start.AddSeconds(3));

            job.Schedule = schedule;

            var writer1 = new StringWriterWithEncoding(Encoding.UTF8);
            var writer2 = new StringWriterWithEncoding(Encoding.UTF8);

            job.Routine = async(parameter, tracker, writer, token) =>
            {
                for (var i = 0; i < 5; i++)
                {
                    await writer.WriteAsync(i.ToString());
                }

                await Task.Delay(200, token);
            };

            job.IsEnabled = true;

            // Act
            var inTime = await timeMachine.WaitUntilSecondsElapse(start, 0.8);

            if (!inTime)
            {
                throw new Exception("Test failed. TPL was too slow.");
            }

            job.Output = writer1;

            await timeMachine.WaitUntilSecondsElapse(start, 1.3);

            job.Output = writer2;

            await timeMachine.WaitUntilSecondsElapse(start, 4.8);

            var info = job.GetInfo(null);

            // Assert
            try
            {
                Assert.That(info.CurrentRun, Is.Null);

                Assert.That(info.RunCount, Is.EqualTo(2));
                Assert.That(info.Runs, Has.Count.EqualTo(2));

                Assert.That(writer1.ToString(), Is.EqualTo("01234"));
                Assert.That(writer2.ToString(), Is.EqualTo("01234"));
            }
            catch (Exception ex)
            {
                var sb = new StringBuilder();
                sb.AppendLine("*** Test Failed ***");
                sb.AppendLine(ex.ToString());
                sb.AppendLine("*** Log: ***");

                var log = _logWriter.ToString();

                sb.AppendLine(log);

                Assert.Fail(sb.ToString());
            }
        }