Skip to content

Commit

Permalink
clean-up test project
Browse files Browse the repository at this point in the history
fixed all Tests
reverted WaitForJobsOrTimeout
added new WaitForJobsOrTimeout that can advance timefor each run
  • Loading branch information
falvarez1 committed Apr 24, 2024
1 parent 0c82894 commit a49372b
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 52 deletions.
1 change: 0 additions & 1 deletion tests/NCronJob.Tests/NCronJob.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.4.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0-release-24177-07" />
<PackageReference Include="Shouldly" Version="4.2.1" />
<PackageReference Include="TimeProviderExtensions" Version="1.0.0" />
<PackageReference Include="xunit" Version="2.7.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.8">
<PrivateAssets>all</PrivateAssets>
Expand Down
85 changes: 59 additions & 26 deletions tests/NCronJob.Tests/NCronJobIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Time.Testing;
using Shouldly;
using TimeProviderExtensions;

namespace NCronJob.Tests;

Expand All @@ -14,35 +14,52 @@ public sealed class NCronJobIntegrationTests : JobIntegrationBase
[Fact]
public async Task CronJobThatIsScheduledEveryMinuteShouldBeExecuted()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>(p => p.WithCronExpression("* * * * *")));
var provider = CreateServiceProvider();

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

fakeTimer.Advance(TimeSpan.FromMinutes(1));
var jobFinished = await WaitForJobsOrTimeout(1);
jobFinished.ShouldBeTrue();
}

[Fact]
public async Task AdvancingTheWholeTimeShouldHaveTenEntries()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>(p => p.WithCronExpression("* * * * *")));
var provider = CreateServiceProvider();

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

var jobFinished = await WaitForJobsOrTimeout(10);
void AdvanceTime() => fakeTimer.Advance(TimeSpan.FromMinutes(1));
var jobFinished = await WaitForJobsOrTimeout(10, AdvanceTime);

jobFinished.ShouldBeTrue();
}

[Fact]
public async Task JobsShouldCancelOnCancellation()
{
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>(p => p.WithCronExpression("* * * * *")));
var provider = CreateServiceProvider();

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

var jobFinished = await DoNotWaitJustCancel(10);
jobFinished.ShouldBeFalse();
}

[Fact]
public async Task EachJobRunHasItsOwnScope()
{
var fakeTimer = new ManualTimeProvider();
var fakeTimer = new FakeTimeProvider();
var storage = new Storage();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddSingleton(storage);
Expand All @@ -63,7 +80,7 @@ public async Task EachJobRunHasItsOwnScope()
[Fact]
public async Task ExecuteAnInstantJob()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>());
var provider = CreateServiceProvider();
Expand All @@ -78,21 +95,23 @@ public async Task ExecuteAnInstantJob()
[Fact]
public async Task CronJobShouldPassDownParameter()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<ParameterJob>(p => p.WithCronExpression("* * * * *").WithParameter("Hello World")));
var provider = CreateServiceProvider();

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

fakeTimer.Advance(TimeSpan.FromMinutes(1));

var content = await CommunicationChannel.Reader.ReadAsync(CancellationToken);
content.ShouldBe("Hello World");
}

[Fact]
public async Task InstantJobShouldGetParameter()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<ParameterJob>());
var provider = CreateServiceProvider();
Expand All @@ -107,36 +126,41 @@ public async Task InstantJobShouldGetParameter()
[Fact]
public async Task CronJobThatIsScheduledEverySecondShouldBeExecuted()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider(TimeSpan.FromSeconds(1));
var fakeTimer = new FakeTimeProvider();
fakeTimer.Advance(TimeSpan.FromSeconds(1));
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>(p => p.WithCronExpression("* * * * * *", true)));
var provider = CreateServiceProvider();

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

var jobFinished = await WaitForJobsOrTimeout(2);
void AdvanceTime() => fakeTimer.Advance(TimeSpan.FromSeconds(1));
var jobFinished = await WaitForJobsOrTimeout(10, AdvanceTime);

jobFinished.ShouldBeTrue();
}

[Fact]
public async Task CanRunSecondPrecisionAndMinutePrecisionJobs()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider(TimeSpan.FromSeconds(1));
var fakeTimer = new FakeTimeProvider();
fakeTimer.Advance(TimeSpan.FromSeconds(1));
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>(
p => p.WithCronExpression("* * * * * *", true).And.WithCronExpression("* * * * *")));
var provider = CreateServiceProvider();

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

var jobFinished = await WaitForJobsOrTimeout(61);
void AdvanceTime() => fakeTimer.Advance(TimeSpan.FromSeconds(1));
var jobFinished = await WaitForJobsOrTimeout(61, AdvanceTime);
jobFinished.ShouldBeTrue();
}

[Fact]
public async Task LongRunningJobShouldNotBlockScheduler()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n
.AddJob<LongRunningJob>(p => p.WithCronExpression("* * * * *"))
Expand All @@ -145,14 +169,15 @@ public async Task LongRunningJobShouldNotBlockScheduler()

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

fakeTimer.Advance(TimeSpan.FromMinutes(1));
var jobFinished = await WaitForJobsOrTimeout(1);
jobFinished.ShouldBeTrue();
}

[Fact]
public async Task NotRegisteredJobShouldNotAbortOtherRuns()
{
var fakeTimer = TimeProviderFactory.GetTimeProvider();
var fakeTimer = new FakeTimeProvider();
ServiceCollection.AddSingleton<TimeProvider>(fakeTimer);
ServiceCollection.AddNCronJob(n => n.AddJob<SimpleJob>(p => p.WithCronExpression("* * * * *")));
ServiceCollection.AddTransient<ParameterJob>();
Expand All @@ -161,21 +186,22 @@ public async Task NotRegisteredJobShouldNotAbortOtherRuns()

await provider.GetRequiredService<IHostedService>().StartAsync(CancellationToken);

fakeTimer.Advance(TimeSpan.FromMinutes(1));
var jobFinished = await WaitForJobsOrTimeout(1);
jobFinished.ShouldBeTrue();
}

[Fact]
public void ThrowIfJobWithDependenciesIsNotRegistered()
public async Task ThrowIfJobWithDependenciesIsNotRegistered()
{
ServiceCollection
.AddNCronJob(n => n.AddJob<JobWithDependency>(p => p.WithCronExpression("* * * * *")));
var provider = CreateServiceProvider();

Assert.Throws<InvalidOperationException>(() =>
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
{
using var executor = new JobExecutor(provider, NullLogger<JobExecutor>.Instance);
executor.RunJob(new RegistryEntry(typeof(JobWithDependency), new JobExecutionContext(null), null), CancellationToken.None);
using var executor = provider.CreateScope().ServiceProvider.GetRequiredService<JobExecutor>();
await executor.RunJob(new RegistryEntry(typeof(JobWithDependency), new JobExecutionContext(null!, null), null), CancellationToken.None);
});
}

Expand All @@ -192,16 +218,23 @@ private sealed class Storage
private sealed class SimpleJob(ChannelWriter<object> writer) : IJob
{
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
=> await writer.WriteAsync(true, token);
{
try
{
context.Output = "Job Completed";
await writer.WriteAsync(context.Output, token);
}
catch (Exception ex)
{
await writer.WriteAsync(ex, token);
}
}
}

private sealed class LongRunningJob : IJob
private sealed class LongRunningJob(TimeProvider timeProvider) : IJob
{
public Task RunAsync(JobExecutionContext context, CancellationToken token)
{
Task.Delay(1000, token).GetAwaiter().GetResult();
return Task.CompletedTask;
}
public async Task RunAsync(JobExecutionContext context, CancellationToken token) =>
await Task.Delay(TimeSpan.FromSeconds(10), timeProvider, token);
}

private sealed class ScopedServiceJob(ChannelWriter<object> writer, Storage storage, GuidGenerator guidGenerator) : IJob
Expand All @@ -224,4 +257,4 @@ private sealed class JobWithDependency(ChannelWriter<object> writer, GuidGenerat
public async Task RunAsync(JobExecutionContext context, CancellationToken token)
=> await writer.WriteAsync(guidGenerator.NewGuid, token);
}
}
}
8 changes: 1 addition & 7 deletions tests/NCronJob.Tests/NCronJobRetryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,6 @@ public async Task JobShouldRetryOnFailure()

fakeTimer.Advance(TimeSpan.FromMinutes(1));

var jobFinished = await WaitForJobsOrTimeout(1);
jobFinished.ShouldBeTrue();

// Validate that the job was retried the correct number of times
// Fail 3 times = 3 retries + 1 success
var attempts = await CommunicationChannel.Reader.ReadAsync(CancellationToken);
Expand All @@ -47,9 +44,6 @@ public async Task JobWithCustomPolicyShouldRetryOnFailure()

fakeTimer.Advance(TimeSpan.FromMinutes(1));

var jobFinished = await WaitForJobsOrTimeout(1);
jobFinished.ShouldBeTrue();

// Validate that the job was retried the correct number of times
// Fail 3 times = 3 retries + 1 success
var attempts = await CommunicationChannel.Reader.ReadAsync(CancellationToken);
Expand Down Expand Up @@ -81,7 +75,7 @@ public async Task RunAsync(JobExecutionContext context, CancellationToken token)
}


[RetryPolicy<MyCustomPolicyCreator>(3, 2)]
[RetryPolicy<MyCustomPolicyCreator>(3, 1)]
private sealed class JobUsingCustomPolicy(ChannelWriter<object> writer, MaxFailuresWrapper maxFailuresWrapper)
: IJob
{
Expand Down
29 changes: 27 additions & 2 deletions tests/NCronJob.Tests/TestHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Runtime.CompilerServices;
using System.Threading.Channels;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Shouldly;

namespace NCronJob.Tests;

Expand Down Expand Up @@ -46,11 +48,24 @@ protected async Task<bool> WaitForJobsOrTimeout(int jobRuns)
await Task.WhenAll(GetCompletionJobs(jobRuns, timeoutTcs.Token));
return true;
}
catch (OperationCanceledException)
catch
{
return false;
}
}

protected async Task<bool> WaitForJobsOrTimeout(int jobRuns, Action timeAdvancer)
{
using var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
await foreach (var jobSuccessful in GetCompletionJobsAsync(jobRuns, timeAdvancer, timeoutCts.Token))
{
jobSuccessful.ShouldBe("Job Completed");
}
return true;
}
catch (Exception)
catch
{
return false;
}
Expand All @@ -77,4 +92,14 @@ protected IEnumerable<Task> GetCompletionJobs(int expectedJobCount, Cancellation
yield return CommunicationChannel.Reader.ReadAsync(cancellationToken).AsTask();
}
}

private async IAsyncEnumerable<object> GetCompletionJobsAsync(int expectedJobCount, Action timeAdvancer, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
for (var i = 0; i < expectedJobCount; i++)
{
timeAdvancer();
var jobResult = await CommunicationChannel.Reader.ReadAsync(cancellationToken);
yield return jobResult;
}
}
}
16 changes: 0 additions & 16 deletions tests/NCronJob.Tests/TimeProviderFactory.cs

This file was deleted.

0 comments on commit a49372b

Please sign in to comment.