public async Task Manager_should_delete_old_stats() { const int expectedRepeats = 3; var age = TimeSpan.FromHours(1); var utcNow = DateTime.UtcNow; var asyncCountdown = new AsyncCountdown("delete old stats", expectedRepeats); var asyncCounter = new AsyncCounter(); var monitorSettings = new Mock<IMonitorSettings>(); monitorSettings.Setup(s => s.StatsHistoryMaxAge).Returns(age); var timeCoordinator = new Mock<ITimeCoordinator>(); timeCoordinator.Setup(c => c.UtcNow).Returns(utcNow); var endpointMetricsCoordinator = new Mock<IEndpointMetricsForwarderCoordinator>(); var repository = new Mock<IEndpointStatsRepository>(); repository.Setup(r => r.DeleteStatisticsOlderThan(utcNow - age, It.IsAny<int>())).Returns(() => { asyncCountdown.Decrement(); asyncCounter.Increment(); const int deletedItemsCount = 127; return asyncCounter.Value >= expectedRepeats ? 0 : deletedItemsCount; }); using (new EndpointStatsManager(repository.Object, monitorSettings.Object, timeCoordinator.Object, endpointMetricsCoordinator.Object)) await asyncCountdown.WaitAsync(MaxTestTime); await Task.Delay(TimeSpan.FromMilliseconds(500)); //it should stop calling cleanup when there are no more items to be cleaned Assert.Equal(expectedRepeats, asyncCounter.Value); }
public async Task Notifier_should_send_notifications_within_specified_time_span() { var minimumChecks = 3; var expectedEventTimeline = new List<string>(); for (var i = 0; i < minimumChecks; ++i) expectedEventTimeline.AddRange(new[] { "updateHealth_start", "delay_start", "update_finish", "update_finish" }); var countdown = new AsyncCountdown("notifications", minimumChecks); var endpointId = Guid.NewGuid(); var interval = TimeSpan.FromMilliseconds(300); SetupHealthCheckInterval(interval); SetupEndpointRegistration(endpointId); _mockClient .Setup(c => c.SendHealthUpdateAsync(endpointId, AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns(() => _awaitableFactory.Return().WithDelay(TimeSpan.FromMilliseconds(100)).WithTimeline("updateHealth").WithCountdown(countdown).RunAsync()); _mockTimeCoordinator .Setup(c => c.Delay(interval, It.IsAny<CancellationToken>())) .Returns(() => _awaitableFactory.Return().WithDelay(TimeSpan.FromMilliseconds(50)).WithTimeline("delay").RunAsync()); using (CreateNotifier()) await countdown.WaitAsync(TestMaxTime); var actualEventTimeline = _awaitableFactory.GetOrderedTimelineEvents() .Select(eventName => (eventName == "updateHealth_finish" || eventName == "delay_finish") ? "update_finish" : eventName) .Take(minimumChecks * 4) .ToArray(); Assert.True(expectedEventTimeline.SequenceEqual(actualEventTimeline), $"Expected:\n{string.Join(",", expectedEventTimeline)}\nGot:\n{string.Join(",", actualEventTimeline)}"); }
public async Task Executor_should_execute_tasks_until_disposal() { var task1Name = "task1"; var task2Name = "task2"; var countdown1 = new AsyncCountdown(task1Name, 10); var counter1 = new AsyncCounter(); var countdown2 = new AsyncCountdown(task2Name, 10); var counter2 = new AsyncCounter(); using (var executor = ContinuousTaskExecutor<string>.StartExecutor(Mock.Of<ITimeCoordinator>())) { Assert.True(executor.TryRegisterTaskFor(task1Name, (item, token) => StartTaskAsync(countdown1, counter1, token))); await countdown1.WaitAsync(_testTimeout); Assert.True(executor.TryRegisterTaskFor(task2Name, (item, token) => StartTaskAsync(countdown2, counter2, token))); await countdown2.WaitAsync(_testTimeout); // check that task 1 still works await countdown1.ResetTo(10).WaitAsync(_testTimeout); } var expected1 = counter1.Value; var expected2 = counter2.Value; await Task.Delay(200); Assert.Equal(expected1, counter1.Value); Assert.Equal(expected2, counter2.Value); }
public async Task Notifier_should_send_current_health_to_the_API() { var endpointId = Guid.NewGuid(); var expectedHealth = new EndpointHealth(HealthStatus.Offline, new Dictionary<string, string> { { "key", "value" } }); HealthUpdate lastCaptured = null; var countdown = new AsyncCountdown("update", 5); SetupHealthCheckInterval(TimeSpan.FromMilliseconds(1)); SetupEndpointRegistration(endpointId); _mockClient.Setup(c => c.SendHealthUpdateAsync(endpointId, AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns((Guid id, string authToken, HealthUpdate upd, CancellationToken token) => _awaitableFactory .Execute(() => lastCaptured = upd) .WithCountdown(countdown) .RunAsync()); using (CreateNotifier(token => Task.FromResult(expectedHealth))) await countdown.WaitAsync(TestMaxTime); Assert.NotNull(lastCaptured); Assert.Equal(expectedHealth.Status, lastCaptured.Status); Assert.Equal(expectedHealth.Details, lastCaptured.Details); Assert.True(lastCaptured.CheckTimeUtc > DateTime.UtcNow.AddMinutes(-1) && lastCaptured.CheckTimeUtc < DateTime.UtcNow.AddMinutes(1)); }
public async Task Notifier_should_send_faulty_health_to_the_API_if_provided_health_method_throws() { var endpointId = Guid.NewGuid(); HealthUpdate lastCaptured = null; var countdown = new AsyncCountdown("update", 5); SetupHealthCheckInterval(TimeSpan.FromMilliseconds(1)); SetupEndpointRegistration(endpointId); _mockClient.Setup(c => c.SendHealthUpdateAsync(endpointId, AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns((Guid id, string authToken, HealthUpdate upd, CancellationToken token) => _awaitableFactory .Execute(() => lastCaptured = upd) .WithCountdown(countdown) .RunAsync()); using (CreateNotifier(async token => { await Task.Delay(50, token); throw new InvalidOperationException("some reason"); })) await countdown.WaitAsync(TestMaxTime); Assert.NotNull(lastCaptured); Assert.Equal(HealthStatus.Faulty, lastCaptured.Status); Assert.Equal("Unable to collect health information", lastCaptured.Details["reason"]); Assert.True(lastCaptured.Details["exception"].StartsWith("System.InvalidOperationException: some reason")); Assert.True(lastCaptured.CheckTimeUtc > DateTime.UtcNow.AddMinutes(-1) && lastCaptured.CheckTimeUtc < DateTime.UtcNow.AddMinutes(1)); }
public async Task Notifier_should_survive_communication_errors_and_eventually_restore_connectivity() { var healthUpdateCountdown = new AsyncCountdown("update", 10); var healthUpdateCountdown2 = new AsyncCountdown("update2", 10); var registrationCountdown = new AsyncCountdown("registration", 10); var intervalCountdown = new AsyncCountdown("interval", 10); var delayCountdown = new AsyncCountdown("delay", 10); var healthCheckInterval = TimeSpan.FromMilliseconds(127); var endpointId = Guid.NewGuid(); var workingHealthUpdate = _awaitableFactory.Return().WithCountdown(healthUpdateCountdown2); var notWorkingHealthUpdate = _awaitableFactory.Throw(new TaskCanceledException()).WithCountdown(healthUpdateCountdown); var currentHealthUpdate = notWorkingHealthUpdate; var notWorkingRegistration = _awaitableFactory.Throw<Guid>(new TaskCanceledException()).WithCountdown(registrationCountdown); var workingRegistration = _awaitableFactory.Return(endpointId); var currentRegistration = notWorkingRegistration; var notWorkingHealthCheckInterval = _awaitableFactory.Throw<TimeSpan>(new TaskCanceledException()).WithCountdown(intervalCountdown); var workingHealthCheckInterval = _awaitableFactory.Return(healthCheckInterval); var currentHealthCheckInterval = notWorkingHealthCheckInterval; _mockClient.Setup(c => c.SendHealthUpdateAsync(It.IsAny<Guid>(), AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns(() => currentHealthUpdate.RunAsync()); _mockClient.Setup(c => c.RegisterEndpointAsync(It.IsAny<EndpointDefinition>(), It.IsAny<CancellationToken>())) .Returns(() => currentRegistration.RunAsync()); _mockClient.Setup(c => c.GetHealthCheckIntervalAsync(It.IsAny<CancellationToken>())) .Returns(() => currentHealthCheckInterval.RunAsync()); _mockTimeCoordinator.Setup(c => c.Delay(healthCheckInterval, It.IsAny<CancellationToken>())) .Returns(() => _awaitableFactory.Return().WithCountdown(delayCountdown).RunAsync()); using (CreateNotifier()) { await registrationCountdown.WaitAsync(TestMaxTime); currentRegistration = workingRegistration; await healthUpdateCountdown.WaitAsync(TestMaxTime); currentHealthUpdate = workingHealthUpdate; await healthUpdateCountdown2.WaitAsync(TestMaxTime); await intervalCountdown.WaitAsync(TestMaxTime); currentHealthCheckInterval = workingHealthCheckInterval; await delayCountdown.WaitAsync(TestMaxTime); } }
public async Task Notifier_should_retry_sending_health_updates_in_case_of_exceptions() { var endpointId = Guid.NewGuid(); SetupEndpointRegistration(endpointId); var checkInterval = TimeSpan.FromMilliseconds(127); SetupHealthCheckInterval(checkInterval); var minRepeats = 10; var countdown = new AsyncCountdown("update", minRepeats); var updates = new ConcurrentQueue<HealthUpdate>(); _mockClient .Setup(c => c.SendHealthUpdateAsync(endpointId, AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns((Guid id, string authToken, HealthUpdate upd, CancellationToken token) => { updates.Enqueue(upd); return _awaitableFactory .Throw(new InvalidOperationException()) .WithCountdown(countdown) .RunAsync(); }); using (CreateNotifier()) await countdown.WaitAsync(TestMaxTime); int expectedSeconds = 1; for (int i = 0; i < minRepeats; ++i) { _mockTimeCoordinator.Verify(c => c.Delay(TimeSpan.FromSeconds(expectedSeconds), It.IsAny<CancellationToken>())); expectedSeconds = Math.Min(expectedSeconds *= 2, MaxEndpointNotifierRetryDelayInSecs); } _mockTimeCoordinator.Verify(c => c.Delay(checkInterval, It.IsAny<CancellationToken>()), Times.Once); Assert.Equal(1, updates.Distinct().Count()); }
public async Task Notifier_should_cancel_health_check_on_dispose() { SetupEndpointRegistration(Guid.NewGuid()); SetupHealthCheckInterval(TimeSpan.FromMilliseconds(1)); var notCancelled = false; var countdown = new AsyncCountdown("healthCheck", 1); Func<CancellationToken, Task<EndpointHealth>> healthCheck = async token => { countdown.Decrement(); await Task.Delay(TestMaxTime, token); notCancelled = true; return new EndpointHealth(HealthStatus.Healthy); }; using (CreateNotifier(healthCheck)) await countdown.WaitAsync(TestMaxTime); Assert.False(notCancelled); }
public async Task Notifier_should_register_endpoint_with_all_details_and_specified_host() { EndpointDefinition captured = null; var countdown = new AsyncCountdown("register", 1); var endpointId = Guid.NewGuid(); _mockClient .Setup(c => c.RegisterEndpointAsync(It.IsAny<EndpointDefinition>(), It.IsAny<CancellationToken>())) .Returns((EndpointDefinition def, CancellationToken token) => _awaitableFactory .Execute(() => { captured = def; return endpointId; }) .WithCountdown(countdown) .RunAsync()); SetupHealthCheckInterval(TimeSpan.FromMilliseconds(1)); Action<IEndpointDefintionBuilder> builder = b => b.DefineName("endpointName") .DefineGroup("endpointGroup") .DefineTags("t1") .DefineAddress("host", "uniqueName") .DefinePassword(AuthenticationToken); using (CreateNotifier(builder, token => Task.FromResult(new EndpointHealth(HealthStatus.Offline)))) await countdown.WaitAsync(TestMaxTime); Assert.NotNull(captured); Assert.Equal("endpointName", captured.EndpointName); Assert.Equal("endpointGroup", captured.GroupName); Assert.Equal(new[] { "t1" }, captured.Tags); Assert.Equal("host:uniqueName", captured.Address); }
public async Task Notifier_should_register_endpoint_again_if_notification_failed_with_EndpointNotFoundException() { var oldEndpointId = Guid.NewGuid(); var newEndpointId = Guid.NewGuid(); var oldEndpointCountdown = new AsyncCountdown("oldEndpoint", 1); var newEndpointCountdown = new AsyncCountdown("newEndpoint", 1); _mockClient .SetupSequence(c => c.RegisterEndpointAsync(It.IsAny<EndpointDefinition>(), It.IsAny<CancellationToken>())) .Returns(Task.FromResult(oldEndpointId)) .Returns(Task.FromResult(newEndpointId)); _mockClient .Setup(c => c.SendHealthUpdateAsync(oldEndpointId, AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns(() => _awaitableFactory .Throw(new EndpointNotFoundException()) .WithCountdown(oldEndpointCountdown) .RunAsync()); _mockClient .Setup(c => c.SendHealthUpdateAsync(newEndpointId, AuthenticationToken, It.IsAny<HealthUpdate>(), It.IsAny<CancellationToken>())) .Returns(() => _awaitableFactory .Return() .WithCountdown(newEndpointCountdown) .RunAsync()); using (CreateNotifier()) { await oldEndpointCountdown.WaitAsync(TestMaxTime); await newEndpointCountdown.WaitAsync(TestMaxTime); } }
public async Task Executor_should_continue_processing_other_tasks_when_one_finish() { var task1Name = "task1"; var task2Name = "task2"; var countdown1 = new AsyncCountdown(task1Name, 10); var task2Finished = new SemaphoreSlim(0); using (var executor = ContinuousTaskExecutor<string>.StartExecutor(Mock.Of<ITimeCoordinator>())) { executor.FinishedTaskFor += item => { if (item == task2Name) task2Finished.Release(); }; Assert.True(executor.TryRegisterTaskFor(task1Name, (item, token) => StartTaskAsync(token, b => b.WithCountdown(countdown1)))); await countdown1.WaitAsync(_testTimeout); Assert.True(executor.TryRegisterTaskFor(task2Name, (item, token) => Task.Delay(25, token))); await task2Finished.WaitAsync(_testTimeout); // check that task 1 still works await countdown1.ResetTo(10).WaitAsync(_testTimeout); } }
private Task StartTaskAsync(AsyncCountdown countdown, AsyncCounter counter, CancellationToken token) { return StartTaskAsync(token, c => c.WithCountdown(countdown).WithCounter(counter)); }
public async Task Executor_should_immediately_break_the_loop_on_cancelation() { var timeCoordinator = new Mock<ITimeCoordinator>(); var countdown = new AsyncCountdown("task", 1); var taskNotCancelled = false; using (var executor = ContinuousTaskExecutor<string>.StartExecutor(timeCoordinator.Object)) { executor.TryRegisterTaskFor("task", async (item, token) => { countdown.Decrement(); await Task.Delay(_testTimeout, token); taskNotCancelled = true; }); await countdown.WaitAsync(_testTimeout); } Assert.False(taskNotCancelled, "Task was not cancelled"); timeCoordinator.Verify(c => c.Delay(It.IsAny<TimeSpan>(), It.IsAny<CancellationToken>()), Times.Never, "Executor should not trigger any delay"); }
public async Task Executor_should_cancel_all_tasks_on_disposal_and_report_all_finished() { var task1NotCancelled = false; var task2NotCancelled = false; var task1 = "task1"; var task2 = "task2"; var task1Ran = new AsyncCountdown(task1, 1); var task2Ran = new AsyncCountdown(task2, 1); var completed = new ConcurrentQueue<string>(); using (var executor = ContinuousTaskExecutor<string>.StartExecutor(Mock.Of<ITimeCoordinator>())) { executor.FinishedTaskFor += item => completed.Enqueue(item); executor.TryRegisterTaskFor(task1, async (item, token) => { task1Ran.Decrement(); await Task.Delay(_testTimeout, token); task1NotCancelled = true; }); executor.TryRegisterTaskFor(task2, async (item, token) => { task2Ran.Decrement(); await Task.Delay(_testTimeout, token); task2NotCancelled = true; }); await task1Ran.WaitAsync(_testTimeout); await task2Ran.WaitAsync(_testTimeout); } Assert.False(task1NotCancelled, "task1NotCancelled"); Assert.False(task2NotCancelled, "task2NotCancelled"); CollectionAssert.AreEquivalent(new[] { task1, task2 }, completed); }