From 68d3d2724cfbd03309d6e0e379ccec7dcdacdc41 Mon Sep 17 00:00:00 2001 From: ngfrolov Date: Tue, 7 Nov 2023 14:19:13 +0500 Subject: [PATCH] Add Tests --- .../Background/BackgroundWorker.cs | 28 ++-- .../Background/PeriodicBackgroundWorker.cs | 6 +- AsbCloudInfrastructure/DependencyInjection.cs | 1 + .../DrillingProgram/DrillingProgramService.cs | 2 +- .../Services/BackgroundWorkertest.cs | 60 ++++++--- .../Services/PeriodicBackgroundWorkerTest.cs | 97 ++++++++++++++ AsbCloudWebApi.Tests/Services/WorkTest.cs | 126 ++++++++++++++++++ 7 files changed, 285 insertions(+), 35 deletions(-) create mode 100644 AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs create mode 100644 AsbCloudWebApi.Tests/Services/WorkTest.cs diff --git a/AsbCloudInfrastructure/Background/BackgroundWorker.cs b/AsbCloudInfrastructure/Background/BackgroundWorker.cs index beaca27e..f56b0ae4 100644 --- a/AsbCloudInfrastructure/Background/BackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/BackgroundWorker.cs @@ -52,17 +52,6 @@ public class BackgroundWorker : BackgroundService this.serviceProvider = serviceProvider; } - /// - /// Добавить в очередь - /// - /// - public void Enqueue(Work work) - { - works.Enqueue(work); - if (ExecuteTask is null || ExecuteTask.IsCompleted) - StartAsync(CancellationToken.None).Wait(); - } - protected override async Task ExecuteAsync(CancellationToken token) { while (!token.IsCancellationRequested && works.TryDequeue(out CurrentWork)) @@ -94,12 +83,27 @@ public class BackgroundWorker : BackgroundService } } + /// + /// Добавить в очередь + /// + /// work.Id может быть не уникальным, + /// при этом метод TryRemoveFromQueue удалит все работы с совпадающими id + /// + /// + /// + public void Enqueue(Work work) + { + works.Enqueue(work); + if (ExecuteTask is null || ExecuteTask.IsCompleted) + StartAsync(CancellationToken.None).Wait(); + } + /// /// Удаление работы по ID из одноразовой очереди /// /// /// - public bool TryRemoveFromRunOnceQueue(string id) + public bool TryRemoveFromQueue(string id) { var work = Works.FirstOrDefault(w => w.Id == id); if (work is not null) diff --git a/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs b/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs index fd12c13a..a7490ed7 100644 --- a/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs +++ b/AsbCloudInfrastructure/Background/PeriodicBackgroundWorker.cs @@ -14,8 +14,8 @@ namespace AsbCloudInfrastructure.Background; /// public class PeriodicBackgroundWorker : BackgroundService { - private static readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); - private static readonly TimeSpan minDelay = TimeSpan.FromSeconds(1); + private readonly TimeSpan executePeriod = TimeSpan.FromSeconds(10); + private readonly TimeSpan minDelay = TimeSpan.FromSeconds(1); private readonly IServiceProvider serviceProvider; private readonly List works = new(8); @@ -97,6 +97,8 @@ public class PeriodicBackgroundWorker : BackgroundService { var periodic = new WorkPeriodic(work, period); works.Add(periodic); + if (ExecuteTask is null || ExecuteTask.IsCompleted) + StartAsync(CancellationToken.None).Wait(); } private WorkPeriodic? GetNext() diff --git a/AsbCloudInfrastructure/DependencyInjection.cs b/AsbCloudInfrastructure/DependencyInjection.cs index 93c6575e..28bde671 100644 --- a/AsbCloudInfrastructure/DependencyInjection.cs +++ b/AsbCloudInfrastructure/DependencyInjection.cs @@ -165,6 +165,7 @@ namespace AsbCloudInfrastructure services.AddSingleton>(provider => TelemetryDataCache.GetInstance(provider)); services.AddSingleton>(provider => TelemetryDataCache.GetInstance(provider)); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(provider => ReduceSamplingService.GetInstance(configuration)); diff --git a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs index 5557707d..c91689a3 100644 --- a/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs +++ b/AsbCloudInfrastructure/Services/DrillingProgram/DrillingProgramService.cs @@ -556,7 +556,7 @@ namespace AsbCloudInfrastructure.Services.DrillingProgram private async Task RemoveDrillingProgramAsync(int idWell, CancellationToken token) { var workId = MakeWorkId(idWell); - backgroundWorker.TryRemoveFromRunOnceQueue(workId); + backgroundWorker.TryRemoveFromQueue(workId); var filesIds = await context.Files .Where(f => f.IdWell == idWell && diff --git a/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs b/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs index a5690d84..0f54dddf 100644 --- a/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs +++ b/AsbCloudWebApi.Tests/Services/BackgroundWorkertest.cs @@ -1,16 +1,7 @@ -using AsbCloudApp.Data; -using AsbCloudApp.Data.SAUB; -using AsbCloudApp.Repositories; -using AsbCloudApp.Requests; -using AsbCloudApp.Services; -using AsbCloudInfrastructure.Background; -using AsbCloudInfrastructure.Services; -using AsbCloudInfrastructure.Services.SAUB; +using AsbCloudInfrastructure.Background; using Microsoft.Extensions.DependencyInjection; using NSubstitute; using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; @@ -18,12 +9,12 @@ using Xunit; namespace AsbCloudWebApi.Tests.Services; -public class BackgroundWorkertest +public class BackgroundWorkerTest { private IServiceProvider provider; private BackgroundWorker service; - public BackgroundWorkertest() + public BackgroundWorkerTest() { provider = Substitute.For(); var serviceScope = Substitute.For(); @@ -33,7 +24,7 @@ public class BackgroundWorkertest service = new BackgroundWorker(provider); typeof(BackgroundWorker) - .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)? + .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance) .SetValue(service, TimeSpan.FromMilliseconds(1)); } @@ -55,11 +46,8 @@ public class BackgroundWorkertest service.Enqueue(work); } - var waitI = workCount; - await Task.Delay(1_000); - //while (waitI-- > 0 && service.ExecuteTask is not null && service.ExecuteTask.IsCompleted) - // await Task.Delay(4); - + await service.ExecuteTask; + //assert Assert.Equal(workCount, result); } @@ -69,15 +57,16 @@ public class BackgroundWorkertest { var expectadResult = 42; var result = 0; + Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) { result = expectadResult; return Task.CompletedTask; } + var goodWork = Work.CreateByDelegate("", workAction); Task failAction(string id, IServiceProvider services, Action callback, CancellationToken token) => throw new Exception(); - var goodWork = Work.CreateByDelegate("", workAction); var badWork = Work.CreateByDelegate("", failAction); badWork.OnErrorAsync = (id, exception, token) => throw new Exception(); @@ -85,9 +74,40 @@ public class BackgroundWorkertest //act service.Enqueue(badWork); service.Enqueue(goodWork); - await Task.Delay(1200); + + await service.ExecuteTask; //assert Assert.Equal(expectadResult, result); + Assert.Equal(1, service.Felled.Count); + Assert.Equal(1, service.Done.Count); + } + + [Fact] + public async Task TryRemove() + { + var workCount = 5; + var result = 0; + Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) + { + result++; + return Task.Delay(10); + } + + //act + for (int i = 0; i < workCount; i++) + { + var work = Work.CreateByDelegate(i.ToString(), workAction); + service.Enqueue(work); + } + + var removed = service.TryRemoveFromQueue((workCount - 1).ToString()); + + await service.ExecuteTask; + + //assert + Assert.True(removed); + Assert.Equal(workCount - 1, result); + Assert.Equal(workCount - 1, service.Done.Count); } } diff --git a/AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs b/AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs new file mode 100644 index 00000000..d0c16285 --- /dev/null +++ b/AsbCloudWebApi.Tests/Services/PeriodicBackgroundWorkerTest.cs @@ -0,0 +1,97 @@ +using AsbCloudInfrastructure.Background; +using DocumentFormat.OpenXml.Drawing.Charts; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AsbCloudWebApi.Tests.Services; + +public class PeriodicBackgroundWorkerTest +{ + private IServiceProvider provider; + private PeriodicBackgroundWorker service; + + public PeriodicBackgroundWorkerTest() + { + provider = Substitute.For(); + var serviceScope = Substitute.For(); + var serviceScopeFactory = Substitute.For(); + serviceScopeFactory.CreateScope().Returns(serviceScope); + ((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory); + + service = new PeriodicBackgroundWorker(provider); + typeof(PeriodicBackgroundWorker) + .GetField("minDelay", BindingFlags.NonPublic | BindingFlags.Instance)? + .SetValue(service, TimeSpan.FromMilliseconds(1)); + + typeof(PeriodicBackgroundWorker) + .GetField("executePeriod", BindingFlags.NonPublic | BindingFlags.Instance)? + .SetValue(service, TimeSpan.FromMilliseconds(1)); + } + + [Fact] + public async Task WorkRunsTwice() + { + var workCount = 2; + var periodMs = 100d; + + var period = TimeSpan.FromMilliseconds(periodMs); + + var result = 0; + Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) + { + result++; + return Task.CompletedTask; + } + + //act + var work = Work.CreateByDelegate("", workAction); + + var stopwatch = Stopwatch.StartNew(); + service.Add(work, period); + + var delay = (periodMs / 20) + (periodMs * workCount) - stopwatch.ElapsedMilliseconds; + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + + //assert + Assert.Equal(workCount, result); + } + + + [Fact] + public async Task Enqueue_continues_after_exceptions() + { + var expectadResult = 42; + var result = 0; + + Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) + { + result = expectadResult; + return Task.CompletedTask; + } + var goodWork = Work.CreateByDelegate("", workAction); + + Task failAction(string id, IServiceProvider services, Action callback, CancellationToken token) + => throw new Exception(); + + var badWork = Work.CreateByDelegate("", failAction); + badWork.OnErrorAsync = (id, exception, token) => throw new Exception(); + + //act + service.Add(badWork, TimeSpan.FromSeconds(2)); + service.Add(goodWork, TimeSpan.FromSeconds(2)); + + await Task.Delay(TimeSpan.FromMilliseconds(20)); + + //assert + Assert.Equal(expectadResult, result); + Assert.Equal(1, badWork.CountErrors); + Assert.Equal(1, goodWork.CountComplete); + Assert.Equal(1, goodWork.CountStart); + } +} diff --git a/AsbCloudWebApi.Tests/Services/WorkTest.cs b/AsbCloudWebApi.Tests/Services/WorkTest.cs new file mode 100644 index 00000000..ea826565 --- /dev/null +++ b/AsbCloudWebApi.Tests/Services/WorkTest.cs @@ -0,0 +1,126 @@ +using AsbCloudInfrastructure.Background; +using Microsoft.Extensions.DependencyInjection; +using NSubstitute; +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace AsbCloudWebApi.Tests.Services +{ + public class WorkTest + { + private IServiceProvider provider; + + public WorkTest() + { + provider = Substitute.For(); + var serviceScope = Substitute.For(); + var serviceScopeFactory = Substitute.For(); + serviceScopeFactory.CreateScope().Returns(serviceScope); + ((ISupportRequiredService)provider).GetRequiredService(typeof(IServiceScopeFactory)).Returns(serviceScopeFactory); + } + + [Fact] + public async Task Work_done_with_success() + { + Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) + => Task.CompletedTask; + + var work = Work.CreateByDelegate("", workAction); + + //act + var begin = DateTime.Now; + await work.Start(provider, CancellationToken.None); + var done = DateTime.Now; + var executionTime = done - begin; + + //assert + Assert.Equal(1, work.CountComplete); + Assert.Equal(1, work.CountStart); + Assert.Equal(0, work.CountErrors); + Assert.Null(work.CurrentState); + Assert.Null(work.LastError); + + var lastState = work.LastComplete; + Assert.NotNull(lastState); + Assert.InRange(lastState.Start, begin, done - 0.5 * executionTime); + Assert.InRange(lastState.End, done - 0.5 * executionTime, done); + Assert.InRange(lastState.ExecutionTime, TimeSpan.Zero, executionTime); + } + + [Fact] + public async Task Work_calls_callback() + { + var expectedState = "42"; + var expectedProgress = 42d; + + var timeout = TimeSpan.FromMilliseconds(40); + + Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) + { + callback.Invoke(expectedState, expectedProgress); + return Task.Delay(timeout); + } + + var work = Work.CreateByDelegate("", workAction); + + //act + var begin = DateTime.Now; + _ = work.Start(provider, CancellationToken.None); + await Task.Delay(timeout/3); + + //assert + Assert.Equal(0, work.CountComplete); + Assert.Equal(1, work.CountStart); + Assert.Equal(0, work.CountErrors); + Assert.NotNull(work.CurrentState); + Assert.Null(work.LastComplete); + Assert.Null(work.LastError); + + var currentState = work.CurrentState; + Assert.NotNull(currentState); + Assert.InRange(currentState.Start, begin, begin + timeout); + Assert.InRange(currentState.StateUpdate, begin, begin + timeout); + Assert.Equal(expectedState, currentState.State); + Assert.Equal(expectedProgress, currentState.Progress); + } + + + [Fact] + public async Task Work_fails_with_info() + { + var expectedState = "41"; + var expectedErrorText = "42"; + var minWorkTime = TimeSpan.FromMilliseconds(10); + + async Task workAction(string id, IServiceProvider services, Action callback, CancellationToken token) + { + await Task.Delay(minWorkTime); + callback(expectedState, 0); + throw new Exception(expectedErrorText); + } + + var work = Work.CreateByDelegate("", workAction); + + //act + var begin = DateTime.Now; + await work.Start(provider, CancellationToken.None); + + //assert + Assert.Equal(0, work.CountComplete); + Assert.Equal(1, work.CountStart); + Assert.Equal(1, work.CountErrors); + Assert.Null(work.CurrentState); + Assert.Null(work.LastComplete); + + var error = work.LastError; + Assert.NotNull(error); + Assert.InRange(error.Start, begin, DateTime.Now); + Assert.InRange(error.End, begin, DateTime.Now); + Assert.InRange(error.ExecutionTime, minWorkTime, DateTime.Now - begin); + Assert.Contains(expectedErrorText, error.ErrorText, StringComparison.InvariantCultureIgnoreCase); + Assert.Equal(expectedState, error.State); + } + } +}