From 46c77c4c8efb85bece83e5ae8461ac737ed97bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sat, 10 Aug 2019 00:49:10 +0300 Subject: [PATCH 01/16] TestJournal with Write intreception and varios failure strategies --- src/Akka.sln | 30 ++ .../Akka.Persistence.TestKit.Tests.csproj | 35 ++ .../TestJournalSpec.cs | 107 ++++++ .../test-journal.conf | 13 + .../Akka.Persistence.TestKit.csproj | 25 ++ .../IJournalWriteInterceptor.cs | 9 + .../JournalWriteBehavior.cs | 357 ++++++++++++++++++ .../JournalWriteInterceptors.cs | 99 +++++ .../Properties/AssemblyInfo.cs | 21 ++ .../Akka.Persistence.TestKit/TestJournal.cs | 105 ++++++ .../TestJournalFailureException.cs | 30 ++ 11 files changed, 831 insertions(+) create mode 100644 src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj create mode 100644 src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs create mode 100644 src/core/Akka.Persistence.TestKit.Tests/test-journal.conf create mode 100644 src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj create mode 100644 src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs create mode 100644 src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs create mode 100644 src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs create mode 100644 src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs create mode 100644 src/core/Akka.Persistence.TestKit/TestJournal.cs create mode 100644 src/core/Akka.Persistence.TestKit/TestJournalFailureException.cs diff --git a/src/Akka.sln b/src/Akka.sln index a825241bba2..94b23d5542f 100644 --- a/src/Akka.sln +++ b/src/Akka.sln @@ -197,6 +197,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpawnBenchmark", "benchmark EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Akka.Persistence.FSharp", "core\Akka.Persistence.FSharp\Akka.Persistence.FSharp.fsproj", "{539C3EB6-FCC8-41FA-9373-364605877EE1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.TestKit", "core\Akka.Persistence.TestKit\Akka.Persistence.TestKit.csproj", "{212A2D35-E8D1-46A7-A1D1-418CF9509D77}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.TestKit.Tests", "core\Akka.Persistence.TestKit.Tests\Akka.Persistence.TestKit.Tests.csproj", "{22F6EA86-0079-41A0-9BD3-82D2D6C34638}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -844,6 +848,30 @@ Global {539C3EB6-FCC8-41FA-9373-364605877EE1}.Release|x64.Build.0 = Release|Any CPU {539C3EB6-FCC8-41FA-9373-364605877EE1}.Release|x86.ActiveCfg = Release|Any CPU {539C3EB6-FCC8-41FA-9373-364605877EE1}.Release|x86.Build.0 = Release|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Debug|Any CPU.Build.0 = Debug|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Debug|x64.ActiveCfg = Debug|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Debug|x64.Build.0 = Debug|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Debug|x86.ActiveCfg = Debug|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Debug|x86.Build.0 = Debug|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Release|Any CPU.ActiveCfg = Release|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Release|Any CPU.Build.0 = Release|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Release|x64.ActiveCfg = Release|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Release|x64.Build.0 = Release|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Release|x86.ActiveCfg = Release|Any CPU + {212A2D35-E8D1-46A7-A1D1-418CF9509D77}.Release|x86.Build.0 = Release|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Debug|x64.ActiveCfg = Debug|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Debug|x64.Build.0 = Debug|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Debug|x86.ActiveCfg = Debug|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Debug|x86.Build.0 = Debug|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|Any CPU.Build.0 = Release|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x64.ActiveCfg = Release|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x64.Build.0 = Release|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x86.ActiveCfg = Release|Any CPU + {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -929,6 +957,8 @@ Global {A1D57384-A933-480A-9DF4-FA5E60AB1A67} = {73108242-625A-4D7B-AA09-63375DBAE464} {9BEAF609-B406-4CCB-9708-6E8DFF764232} = {73108242-625A-4D7B-AA09-63375DBAE464} {539C3EB6-FCC8-41FA-9373-364605877EE1} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} + {212A2D35-E8D1-46A7-A1D1-418CF9509D77} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} + {22F6EA86-0079-41A0-9BD3-82D2D6C34638} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164} diff --git a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj new file mode 100644 index 00000000000..05ef7dd9692 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj @@ -0,0 +1,35 @@ + + + + + Akka.Persistence.TestKit.Tests + $(NetFrameworkTestVersion);$(NetCoreTestVersion) + false + + + + + + + + + + + + + + + + + + + + + + $(DefineConstants);CORECLR + + + + $(DefineConstants);RELEASE + + diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs new file mode 100644 index 00000000000..b76f84c478c --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -0,0 +1,107 @@ +namespace Akka.Persistence.TestKit.Tests +{ + using System; + using Actor; + using Akka.Persistence.TestKit; + using Akka.TestKit; + using Configuration; + using Xunit; + using Xunit.Abstractions; + + public class TestJournalSpec : AkkaSpec + { + public TestJournalSpec(ITestOutputHelper output = null) + : base(GetConfig().ToString(), output) + { + } + + [Fact] + public void must_return_ack_after_new_write_interceptor_is_set() + { + var journalActor = GetJournalRef(Sys); + + journalActor.Tell(new TestJournal.UseWriteInterceptor(null), TestActor); + + ExpectMsg(TimeSpan.FromSeconds(3)); + } + + [Fact] + public void works_as_memory_journal_by_default() + { + var journal = TestJournal.FromRef(GetJournalRef(Sys)); + + var actor = Sys.ActorOf(); + + // should pass + journal.OnWrite.Pass(); + actor.Tell("write", TestActor); + ExpectMsg("ack", TimeSpan.FromSeconds(3)); + } + + [Fact] + public void when_fail_on_write_is_set_all_writes_to_journal_will_fail() + { + var journal = TestJournal.FromRef(GetJournalRef(Sys)); + var actor = Sys.ActorOf(); + Watch(actor); + + journal.OnWrite.Fail(); + actor.Tell("write", TestActor); + + ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + } + + [Fact] + public void when_reject_on_write_is_set_all_writes_to_journal_will_be_rejected() + { + var journal = TestJournal.FromRef(GetJournalRef(Sys)); + var actor = Sys.ActorOf(); + Watch(actor); + + journal.OnWrite.Reject(); + actor.Tell("write", TestActor); + + ExpectMsg("rejected", TimeSpan.FromSeconds(3)); + } + + static IActorRef GetJournalRef(ActorSystem sys) => Persistence.Instance.Apply(sys).JournalFor(null); + + static Config GetConfig() + => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.Tests.test-journal.conf"); + } + + public class PersistActor : UntypedPersistentActor + { + public override string PersistenceId => "foo"; + + protected override void OnCommand(object message) + { + var sender = Sender; + switch (message as string) + { + case "write": + Persist(message, _ => + { + sender.Tell("ack"); + }); + + break; + + default: + return; + } + } + + protected override void OnRecover(object message) + { + // noop + } + + protected override void OnPersistRejected(Exception cause, object @event, long sequenceNr) + { + Sender.Tell("rejected"); + + base.OnPersistRejected(cause, @event, sequenceNr); + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit.Tests/test-journal.conf b/src/core/Akka.Persistence.TestKit.Tests/test-journal.conf new file mode 100644 index 00000000000..5598b81eefe --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/test-journal.conf @@ -0,0 +1,13 @@ +akka { + persistence { + journal { + plugin = "akka.persistence.journal.test" + auto-start-journals = ["akka.persistence.journal.test"] + + test { + class = "Akka.Persistence.TestKit.TestJournal, Akka.Persistence.TestKit" + plugin-dispatcher = "akka.actor.default-dispatcher" + } + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj new file mode 100644 index 00000000000..5b3aa096318 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj @@ -0,0 +1,25 @@ + + + + + Akka.Persistence.TestKit + TestKit for writing tests for Akka.NET Persistance module. + $(NetFrameworkLibVersion);$(NetStandardLibVersion) + $(AkkaPackageTags);testkit;persistance + true + 1.6.1 + + + + + + + + $(DefineConstants);RELEASE + + + + + true + + \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs b/src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs new file mode 100644 index 00000000000..9b88eb65960 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs @@ -0,0 +1,9 @@ +namespace Akka.Persistence.TestKit +{ + using System.Threading.Tasks; + + public interface IJournalWriteInterceptor + { + Task InterceptAsync(IPersistentRepresentation message); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs b/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs new file mode 100644 index 00000000000..87810ab6215 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs @@ -0,0 +1,357 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + using Actor; + + public sealed class JournalWriteBehavior + { + internal JournalWriteBehavior(IActorRef journal) + { + this.journal = journal; + } + + private readonly IActorRef journal; + + private void SetInterceptor(IJournalWriteInterceptor interceptor) + => journal.Ask( + new TestJournal.UseWriteInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ).Wait(); + + public void Pass() => SetInterceptor(JournalWriteInterceptors.Noop.Instance); + + public void PassWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + SetInterceptor(new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Noop.Instance)); + } + + public void Fail() => SetInterceptor(JournalWriteInterceptors.Failure.Instance); + + public void FailOnType() => FailOnType(typeof(TMessage)); + + public void FailOnType(Type messageType) + { + if (messageType is null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + SetInterceptor(new JournalWriteInterceptors.OnType(messageType, JournalWriteInterceptors.Failure.Instance)); + } + + public void FailIf(Func predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance)); + } + + public void FailIf(Func> predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance)); + } + + public void FailUnless(Func predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance, negate: true)); + } + + public void FailUnless(Func> predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance, negate: true)); + } + + public void FailWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + SetInterceptor(new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance)); + } + + public void FailIfWithDelay(TimeSpan delay, Func> predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance) + )); + } + + public void FailIfWithDelay(TimeSpan delay, Func predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance) + )); + } + + public void FailUnlessWithDelay(TimeSpan delay, Func predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance), + negate: true + )); + } + + public void FailUnlessWithDelay(TimeSpan delay, Func> predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance), + negate: true + )); + } + + public void FailOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + + public void FailOnTypeWithDelay(TimeSpan delay, Type messageType) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (messageType is null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + SetInterceptor(new JournalWriteInterceptors.OnType( + messageType, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance) + )); + } + + public void Reject() => SetInterceptor(JournalWriteInterceptors.Rejection.Instance); + + public void RejectOnType() => FailOnType(typeof(TMessage)); + + public void RejectOnType(Type messageType) + { + if (messageType is null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + SetInterceptor(new JournalWriteInterceptors.OnType(messageType, JournalWriteInterceptors.Rejection.Instance)); + } + + public void RejectIf(Func predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance)); + } + + public void RejectIf(Func> predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance)); + } + + public void RejectUnless(Func predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance, negate: true)); + } + + public void RejectUnless(Func> predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance, negate: true)); + } + + public void RejectWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + SetInterceptor(new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance)); + } + + public void RejectIfWithDelay(TimeSpan delay, Func> predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance) + )); + } + + public void RejectIfWithDelay(TimeSpan delay, Func predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance) + )); + } + + public void RejectUnlessWithDelay(TimeSpan delay, Func predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance), + negate: true + )); + } + + public void RejectUnlessWithDelay(TimeSpan delay, Func> predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalWriteInterceptors.OnCondition( + predicate, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance), + negate: true + )); + } + + public void RejectOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + + public void RejectOnTypeWithDelay(TimeSpan delay, Type messageType) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (messageType is null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + SetInterceptor(new JournalWriteInterceptors.OnType( + messageType, + new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance) + )); + } + } +} diff --git a/src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs b/src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs new file mode 100644 index 00000000000..aa8f28a2e82 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs @@ -0,0 +1,99 @@ +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + + internal static class JournalWriteInterceptors + { + internal class Noop : IJournalWriteInterceptor + { + public static readonly IJournalWriteInterceptor Instance = new Noop(); + + public Task InterceptAsync(IPersistentRepresentation message) => Task.FromResult(true); + } + + internal class Failure : IJournalWriteInterceptor + { + public static readonly IJournalWriteInterceptor Instance = new Failure(); + + public Task InterceptAsync(IPersistentRepresentation message) => throw new TestJournalFailureException(); + } + + internal class Rejection : IJournalWriteInterceptor + { + public static readonly IJournalWriteInterceptor Instance = new Rejection(); + + public Task InterceptAsync(IPersistentRepresentation message) => throw new TestJournalRejectionException(); + } + + internal class Delay : IJournalWriteInterceptor + { + public Delay(TimeSpan delay, IJournalWriteInterceptor next) + { + _delay = delay; + _next = next; + } + + private readonly TimeSpan _delay; + private readonly IJournalWriteInterceptor _next; + + public async Task InterceptAsync(IPersistentRepresentation message) + { + await Task.Delay(_delay); + await _next.InterceptAsync(message); + } + } + + internal sealed class OnCondition : IJournalWriteInterceptor + { + public OnCondition(Func> predicate, IJournalWriteInterceptor next, bool negate = false) + { + _predicate = predicate; + _next = next; + _negate = negate; + } + + public OnCondition(Func predicate, IJournalWriteInterceptor next, bool negate = false) + { + _predicate = message => Task.FromResult(predicate(message)); + _next = next; + _negate = negate; + } + + private readonly Func> _predicate; + private readonly IJournalWriteInterceptor _next; + private readonly bool _negate; + + public async Task InterceptAsync(IPersistentRepresentation message) + { + var result = await _predicate(message); + if ((_negate && !result) || (!_negate && result)) + { + await _next.InterceptAsync(message); + } + } + } + + internal class OnType : IJournalWriteInterceptor + { + public OnType(Type messageType, IJournalWriteInterceptor next) + { + _messageType = messageType; + _next = next; + } + + private readonly Type _messageType; + private readonly IJournalWriteInterceptor _next; + + public async Task InterceptAsync(IPersistentRepresentation message) + { + var type = message.Payload.GetType(); + + if (_messageType.IsAssignableFrom(type)) + { + await _next.InterceptAsync(message); + } + } + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs b/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..42165dc88ff --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2018 Lightbend Inc. +// Copyright (C) 2013-2018 .NET Foundation +// +//----------------------------------------------------------------------- + +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("66023c4f-f246-446d-b212-2b8f20755671")] diff --git a/src/core/Akka.Persistence.TestKit/TestJournal.cs b/src/core/Akka.Persistence.TestKit/TestJournal.cs new file mode 100644 index 00000000000..253dbecbf17 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/TestJournal.cs @@ -0,0 +1,105 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using Akka.Actor; + using Akka.Persistence; + using Akka.Persistence.Journal; + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Threading.Tasks; + + public interface ITestJournal + { + JournalWriteBehavior OnWrite { get; } + } + + public sealed class TestJournal : MemoryJournal + { + private IJournalWriteInterceptor _writeInterceptor = JournalWriteInterceptors.Noop.Instance; + + protected override bool ReceivePluginInternal(object message) + { + switch (message) + { + case UseWriteInterceptor use: + _writeInterceptor = use.Interceptor; + Sender.Tell(Ack.Instance); + return true; + + default: + return base.ReceivePluginInternal(message); + } + } + + protected override async Task> WriteMessagesAsync(IEnumerable messages) + { + var exceptions = new List(); + foreach (var w in messages) + { + foreach (var p in (IEnumerable) w.Payload) + { + try + { + await _writeInterceptor.InterceptAsync(p); + Add(p); + exceptions.Add(null); + } + catch (TestJournalRejectionException rejected) + { + exceptions.Add(rejected); + } + catch (TestJournalFailureException) + { + throw; + } + } + } + + return exceptions.ToImmutableList(); + } + + public override Task ReplayMessagesAsync(IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, Action recoveryCallback) + { + return base.ReplayMessagesAsync(context, persistenceId, fromSequenceNr, toSequenceNr, max, recoveryCallback); + } + + public static ITestJournal FromRef(IActorRef actor) + { + return new TestJournalWrapper(actor); + } + + public sealed class UseWriteInterceptor + { + public UseWriteInterceptor(IJournalWriteInterceptor interceptor) + { + Interceptor = interceptor; + } + + public IJournalWriteInterceptor Interceptor { get; } + } + + public sealed class Ack + { + public static readonly Ack Instance = new Ack(); + } + + internal class TestJournalWrapper : ITestJournal + { + public TestJournalWrapper(IActorRef actor) + { + _actor = actor; + } + + private readonly IActorRef _actor; + + public JournalWriteBehavior OnWrite => new JournalWriteBehavior(_actor); + } + } +} diff --git a/src/core/Akka.Persistence.TestKit/TestJournalFailureException.cs b/src/core/Akka.Persistence.TestKit/TestJournalFailureException.cs new file mode 100644 index 00000000000..ea167d2efa3 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/TestJournalFailureException.cs @@ -0,0 +1,30 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class TestJournalFailureException : Exception + { + public TestJournalFailureException() { } + public TestJournalFailureException(string message) : base(message) { } + public TestJournalFailureException(string message, Exception inner) : base(message, inner) { } + protected TestJournalFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } + + [Serializable] + public class TestJournalRejectionException : Exception + { + public TestJournalRejectionException() { } + public TestJournalRejectionException(string message) : base(message) { } + public TestJournalRejectionException(string message, Exception inner) : base(message, inner) { } + protected TestJournalRejectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} \ No newline at end of file From 10ac5b296be8b323a456d30813000a7bf0553bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sat, 10 Aug 2019 15:21:33 +0300 Subject: [PATCH 02/16] unit tests for message interceptors --- .../JournalWriteInterceptorsSpecs.cs | 184 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 3 + 2 files changed, 187 insertions(+) create mode 100644 src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs diff --git a/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs b/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs new file mode 100644 index 00000000000..c024a807995 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs @@ -0,0 +1,184 @@ +namespace Akka.Persistence.TestKit.Tests +{ + using System; + using System.Threading.Tasks; + using Akka.Persistence.TestKit; + using FluentAssertions; + using Xunit; + + public class JournalWriteInterceptorsSpecs + { + [Fact] + public void noop_immediately_returns_without_exception() + { + JournalWriteInterceptors.Noop.Instance + .Awaiting(x => x.InterceptAsync(null)) + .ShouldNotThrow(); + } + + [Fact] + public void failure_must_throw_specific_exception() + { + JournalWriteInterceptors.Failure.Instance + .Awaiting(x => x.InterceptAsync(null)) + .ShouldThrowExactly(); + } + + [Fact] + public void rejection_must_throw_specific_exception() + { + JournalWriteInterceptors.Rejection.Instance + .Awaiting(x => x.InterceptAsync(null)) + .ShouldThrowExactly(); + } + + [Fact] + public async Task delay_must_call_next_interceptor_after_specified_delay() + { + var duration = TimeSpan.FromMilliseconds(100); + var probe = new InterceptorProbe(); + var delay = new JournalWriteInterceptors.Delay(duration, probe); + + var startedAt = DateTime.Now; + await delay.InterceptAsync(null); + + probe.WasCalled.Should().BeTrue(); + probe.CalledAt.Should().BeOnOrAfter(startedAt + duration); + } + + [Fact] + public async Task on_type_must_call_next_interceptor_when_message_is_exactly_awaited_type() + { + var probe = new InterceptorProbe(); + var onType = new JournalWriteInterceptors.OnType(typeof(SpecificMessage), probe); + var message = new Persistent(new SpecificMessage()); + + await onType.InterceptAsync(message); + + probe.WasCalled.Should().BeTrue(); + probe.Message.Should().BeSameAs(message); + } + + [Fact] + public async Task on_type_must_call_next_interceptor_when_message_is_subclass_of_awaited_type() + { + var probe = new InterceptorProbe(); + var onType = new JournalWriteInterceptors.OnType(typeof(SpecificMessage), probe); + var message = new Persistent(new SubclassMessage()); + + await onType.InterceptAsync(message); + + probe.WasCalled.Should().BeTrue(); + probe.Message.Should().BeSameAs(message); + } + + [Fact] + public async Task on_type_must_call_next_interceptor_when_message_is_implements_awaited_interface_type() + { + var probe = new InterceptorProbe(); + var onType = new JournalWriteInterceptors.OnType(typeof(IMessageWithInterface), probe); + var message = new Persistent(new MessageWithInterface()); + + await onType.InterceptAsync(message); + + probe.WasCalled.Should().BeTrue(); + probe.Message.Should().BeSameAs(message); + } + + [Fact] + public async Task on_type_must_not_call_next_interceptor_when_message_does_not_correspond_to_described_rules() + { + var probe = new InterceptorProbe(); + var onType = new JournalWriteInterceptors.OnType(typeof(SubclassMessage), probe); + var message = new Persistent(new SpecificMessage()); + + await onType.InterceptAsync(message); + + probe.WasCalled.Should().BeFalse(); + } + + [Fact] + public async Task on_condition_must_accept_sync_lambda() + { + var probe = new InterceptorProbe(); + var onCondition = new JournalWriteInterceptors.OnCondition(_ => true, probe); + + await onCondition.InterceptAsync(null); + + probe.WasCalled.Should().BeTrue(); + } + + [Fact] + public async Task on_condition_must_accept_async_lambda() + { + var probe = new InterceptorProbe(); + var onCondition = new JournalWriteInterceptors.OnCondition(_ => Task.FromResult(true), probe); + + await onCondition.InterceptAsync(null); + + probe.WasCalled.Should().BeTrue(); + } + + [Fact] + public async Task on_condition_must_call_next_interceptor_unless_predicate_returns_false() + { + var probe = new InterceptorProbe(); + var onCondition = new JournalWriteInterceptors.OnCondition(_ => false, probe); + + await onCondition.InterceptAsync(null); + + probe.WasCalled.Should().BeFalse(); + } + + [Fact] + public async Task on_condition_with_negation_must_call_next_interceptor_unless_predicate_returns_true() + { + var probe = new InterceptorProbe(); + var onCondition = new JournalWriteInterceptors.OnCondition(_ => false, probe, negate: true); + + await onCondition.InterceptAsync(null); + + probe.WasCalled.Should().BeTrue(); + } + + [Fact] + public async Task on_condition_must_pass_the_same_message_to_predicate() + { + var probe = new InterceptorProbe(); + var expectedMessage = new Persistent("test"); + + var onCondition = new JournalWriteInterceptors.OnCondition(message => + { + message.Should().BeSameAs(expectedMessage); + return false; + }, probe); + + await onCondition.InterceptAsync(expectedMessage); + } + + + private class SpecificMessage { } + + private class SubclassMessage : SpecificMessage { } + + private interface IMessageWithInterface { } + + private class MessageWithInterface : IMessageWithInterface { } + + private class InterceptorProbe : IJournalWriteInterceptor + { + public bool WasCalled { get; private set; } + public DateTime CalledAt { get; private set; } + public IPersistentRepresentation Message { get; private set; } + + public Task InterceptAsync(IPersistentRepresentation message) + { + CalledAt = DateTime.Now; + WasCalled = true; + Message = message; + + return Task.CompletedTask; + } + } + } +} diff --git a/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs b/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs index 42165dc88ff..aed9a510eed 100644 --- a/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs +++ b/src/core/Akka.Persistence.TestKit/Properties/AssemblyInfo.cs @@ -6,6 +6,7 @@ //----------------------------------------------------------------------- using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following @@ -19,3 +20,5 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("66023c4f-f246-446d-b212-2b8f20755671")] + +[assembly: InternalsVisibleTo("Akka.Persistence.TestKit.Tests")] From 42d7f8c80973d93237e5bb2ade0426febaccfe16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sat, 10 Aug 2019 16:58:01 +0300 Subject: [PATCH 03/16] TestJournal OnRecovery behavior --- .../JournalWriteInterceptorsSpecs.cs | 28 +-- .../IJournalBehaviorSetter.cs | 49 ++++ ...eInterceptor.cs => IJournalInterceptor.cs} | 2 +- .../Akka.Persistence.TestKit/ITestJournal.cs | 16 ++ ...Interceptors.cs => JournalInterceptors.cs} | 34 +-- .../JournalRecoveryBehavior.cs | 194 +++++++++++++++ .../JournalWriteBehavior.cs | 222 ++---------------- .../Akka.Persistence.TestKit/TestJournal.cs | 57 ++++- 8 files changed, 356 insertions(+), 246 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs rename src/core/Akka.Persistence.TestKit/{IJournalWriteInterceptor.cs => IJournalInterceptor.cs} (76%) create mode 100644 src/core/Akka.Persistence.TestKit/ITestJournal.cs rename src/core/Akka.Persistence.TestKit/{JournalWriteInterceptors.cs => JournalInterceptors.cs} (66%) create mode 100644 src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs diff --git a/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs b/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs index c024a807995..24fc63793fb 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs @@ -11,7 +11,7 @@ public class JournalWriteInterceptorsSpecs [Fact] public void noop_immediately_returns_without_exception() { - JournalWriteInterceptors.Noop.Instance + JournalInterceptors.Noop.Instance .Awaiting(x => x.InterceptAsync(null)) .ShouldNotThrow(); } @@ -19,7 +19,7 @@ public void noop_immediately_returns_without_exception() [Fact] public void failure_must_throw_specific_exception() { - JournalWriteInterceptors.Failure.Instance + JournalInterceptors.Failure.Instance .Awaiting(x => x.InterceptAsync(null)) .ShouldThrowExactly(); } @@ -27,7 +27,7 @@ public void failure_must_throw_specific_exception() [Fact] public void rejection_must_throw_specific_exception() { - JournalWriteInterceptors.Rejection.Instance + JournalInterceptors.Rejection.Instance .Awaiting(x => x.InterceptAsync(null)) .ShouldThrowExactly(); } @@ -37,7 +37,7 @@ public async Task delay_must_call_next_interceptor_after_specified_delay() { var duration = TimeSpan.FromMilliseconds(100); var probe = new InterceptorProbe(); - var delay = new JournalWriteInterceptors.Delay(duration, probe); + var delay = new JournalInterceptors.Delay(duration, probe); var startedAt = DateTime.Now; await delay.InterceptAsync(null); @@ -50,7 +50,7 @@ public async Task delay_must_call_next_interceptor_after_specified_delay() public async Task on_type_must_call_next_interceptor_when_message_is_exactly_awaited_type() { var probe = new InterceptorProbe(); - var onType = new JournalWriteInterceptors.OnType(typeof(SpecificMessage), probe); + var onType = new JournalInterceptors.OnType(typeof(SpecificMessage), probe); var message = new Persistent(new SpecificMessage()); await onType.InterceptAsync(message); @@ -63,7 +63,7 @@ public async Task on_type_must_call_next_interceptor_when_message_is_exactly_awa public async Task on_type_must_call_next_interceptor_when_message_is_subclass_of_awaited_type() { var probe = new InterceptorProbe(); - var onType = new JournalWriteInterceptors.OnType(typeof(SpecificMessage), probe); + var onType = new JournalInterceptors.OnType(typeof(SpecificMessage), probe); var message = new Persistent(new SubclassMessage()); await onType.InterceptAsync(message); @@ -76,7 +76,7 @@ public async Task on_type_must_call_next_interceptor_when_message_is_subclass_of public async Task on_type_must_call_next_interceptor_when_message_is_implements_awaited_interface_type() { var probe = new InterceptorProbe(); - var onType = new JournalWriteInterceptors.OnType(typeof(IMessageWithInterface), probe); + var onType = new JournalInterceptors.OnType(typeof(IMessageWithInterface), probe); var message = new Persistent(new MessageWithInterface()); await onType.InterceptAsync(message); @@ -89,7 +89,7 @@ public async Task on_type_must_call_next_interceptor_when_message_is_implements_ public async Task on_type_must_not_call_next_interceptor_when_message_does_not_correspond_to_described_rules() { var probe = new InterceptorProbe(); - var onType = new JournalWriteInterceptors.OnType(typeof(SubclassMessage), probe); + var onType = new JournalInterceptors.OnType(typeof(SubclassMessage), probe); var message = new Persistent(new SpecificMessage()); await onType.InterceptAsync(message); @@ -101,7 +101,7 @@ public async Task on_type_must_not_call_next_interceptor_when_message_does_not_c public async Task on_condition_must_accept_sync_lambda() { var probe = new InterceptorProbe(); - var onCondition = new JournalWriteInterceptors.OnCondition(_ => true, probe); + var onCondition = new JournalInterceptors.OnCondition(_ => true, probe); await onCondition.InterceptAsync(null); @@ -112,7 +112,7 @@ public async Task on_condition_must_accept_sync_lambda() public async Task on_condition_must_accept_async_lambda() { var probe = new InterceptorProbe(); - var onCondition = new JournalWriteInterceptors.OnCondition(_ => Task.FromResult(true), probe); + var onCondition = new JournalInterceptors.OnCondition(_ => Task.FromResult(true), probe); await onCondition.InterceptAsync(null); @@ -123,7 +123,7 @@ public async Task on_condition_must_accept_async_lambda() public async Task on_condition_must_call_next_interceptor_unless_predicate_returns_false() { var probe = new InterceptorProbe(); - var onCondition = new JournalWriteInterceptors.OnCondition(_ => false, probe); + var onCondition = new JournalInterceptors.OnCondition(_ => false, probe); await onCondition.InterceptAsync(null); @@ -134,7 +134,7 @@ public async Task on_condition_must_call_next_interceptor_unless_predicate_retur public async Task on_condition_with_negation_must_call_next_interceptor_unless_predicate_returns_true() { var probe = new InterceptorProbe(); - var onCondition = new JournalWriteInterceptors.OnCondition(_ => false, probe, negate: true); + var onCondition = new JournalInterceptors.OnCondition(_ => false, probe, negate: true); await onCondition.InterceptAsync(null); @@ -147,7 +147,7 @@ public async Task on_condition_must_pass_the_same_message_to_predicate() var probe = new InterceptorProbe(); var expectedMessage = new Persistent("test"); - var onCondition = new JournalWriteInterceptors.OnCondition(message => + var onCondition = new JournalInterceptors.OnCondition(message => { message.Should().BeSameAs(expectedMessage); return false; @@ -165,7 +165,7 @@ private interface IMessageWithInterface { } private class MessageWithInterface : IMessageWithInterface { } - private class InterceptorProbe : IJournalWriteInterceptor + private class InterceptorProbe : IJournalInterceptor { public bool WasCalled { get; private set; } public DateTime CalledAt { get; private set; } diff --git a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs new file mode 100644 index 00000000000..e3e797adcaf --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using Actor; + + public interface IJournalBehaviorSetter + { + void SetInterceptor(IJournalInterceptor interceptor); + } + + internal class JournalWriteBehaviorSetter : IJournalBehaviorSetter + { + internal JournalWriteBehaviorSetter(IActorRef journal) + { + this._journal = journal; + } + + private readonly IActorRef _journal; + + public void SetInterceptor(IJournalInterceptor interceptor) + => _journal.Ask( + new TestJournal.UseWriteInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ).Wait(); + } + + internal class JournalRecoveryBehaviorSetter : IJournalBehaviorSetter + { + internal JournalRecoveryBehaviorSetter(IActorRef journal) + { + this._journal = journal; + } + + private readonly IActorRef _journal; + + public void SetInterceptor(IJournalInterceptor interceptor) + => _journal.Ask( + new TestJournal.UseWriteInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ).Wait(); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs b/src/core/Akka.Persistence.TestKit/IJournalInterceptor.cs similarity index 76% rename from src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs rename to src/core/Akka.Persistence.TestKit/IJournalInterceptor.cs index 9b88eb65960..cdf2c6d4aaf 100644 --- a/src/core/Akka.Persistence.TestKit/IJournalWriteInterceptor.cs +++ b/src/core/Akka.Persistence.TestKit/IJournalInterceptor.cs @@ -2,7 +2,7 @@ { using System.Threading.Tasks; - public interface IJournalWriteInterceptor + public interface IJournalInterceptor { Task InterceptAsync(IPersistentRepresentation message); } diff --git a/src/core/Akka.Persistence.TestKit/ITestJournal.cs b/src/core/Akka.Persistence.TestKit/ITestJournal.cs new file mode 100644 index 00000000000..314a6159c1d --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/ITestJournal.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + public interface ITestJournal + { + JournalWriteBehavior OnWrite { get; } + + JournalRecoveryBehavior OnRecovery { get; } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs b/src/core/Akka.Persistence.TestKit/JournalInterceptors.cs similarity index 66% rename from src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs rename to src/core/Akka.Persistence.TestKit/JournalInterceptors.cs index aa8f28a2e82..ebcc279335c 100644 --- a/src/core/Akka.Persistence.TestKit/JournalWriteInterceptors.cs +++ b/src/core/Akka.Persistence.TestKit/JournalInterceptors.cs @@ -3,39 +3,39 @@ using System; using System.Threading.Tasks; - internal static class JournalWriteInterceptors + internal static class JournalInterceptors { - internal class Noop : IJournalWriteInterceptor + internal class Noop : IJournalInterceptor { - public static readonly IJournalWriteInterceptor Instance = new Noop(); + public static readonly IJournalInterceptor Instance = new Noop(); public Task InterceptAsync(IPersistentRepresentation message) => Task.FromResult(true); } - internal class Failure : IJournalWriteInterceptor + internal class Failure : IJournalInterceptor { - public static readonly IJournalWriteInterceptor Instance = new Failure(); + public static readonly IJournalInterceptor Instance = new Failure(); public Task InterceptAsync(IPersistentRepresentation message) => throw new TestJournalFailureException(); } - internal class Rejection : IJournalWriteInterceptor + internal class Rejection : IJournalInterceptor { - public static readonly IJournalWriteInterceptor Instance = new Rejection(); + public static readonly IJournalInterceptor Instance = new Rejection(); public Task InterceptAsync(IPersistentRepresentation message) => throw new TestJournalRejectionException(); } - internal class Delay : IJournalWriteInterceptor + internal class Delay : IJournalInterceptor { - public Delay(TimeSpan delay, IJournalWriteInterceptor next) + public Delay(TimeSpan delay, IJournalInterceptor next) { _delay = delay; _next = next; } private readonly TimeSpan _delay; - private readonly IJournalWriteInterceptor _next; + private readonly IJournalInterceptor _next; public async Task InterceptAsync(IPersistentRepresentation message) { @@ -44,16 +44,16 @@ public async Task InterceptAsync(IPersistentRepresentation message) } } - internal sealed class OnCondition : IJournalWriteInterceptor + internal sealed class OnCondition : IJournalInterceptor { - public OnCondition(Func> predicate, IJournalWriteInterceptor next, bool negate = false) + public OnCondition(Func> predicate, IJournalInterceptor next, bool negate = false) { _predicate = predicate; _next = next; _negate = negate; } - public OnCondition(Func predicate, IJournalWriteInterceptor next, bool negate = false) + public OnCondition(Func predicate, IJournalInterceptor next, bool negate = false) { _predicate = message => Task.FromResult(predicate(message)); _next = next; @@ -61,7 +61,7 @@ public OnCondition(Func predicate, IJournalWrit } private readonly Func> _predicate; - private readonly IJournalWriteInterceptor _next; + private readonly IJournalInterceptor _next; private readonly bool _negate; public async Task InterceptAsync(IPersistentRepresentation message) @@ -74,16 +74,16 @@ public async Task InterceptAsync(IPersistentRepresentation message) } } - internal class OnType : IJournalWriteInterceptor + internal class OnType : IJournalInterceptor { - public OnType(Type messageType, IJournalWriteInterceptor next) + public OnType(Type messageType, IJournalInterceptor next) { _messageType = messageType; _next = next; } private readonly Type _messageType; - private readonly IJournalWriteInterceptor _next; + private readonly IJournalInterceptor _next; public async Task InterceptAsync(IPersistentRepresentation message) { diff --git a/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs b/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs new file mode 100644 index 00000000000..19764219af5 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs @@ -0,0 +1,194 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + + public class JournalRecoveryBehavior + { + internal JournalRecoveryBehavior(IJournalBehaviorSetter setter) + { + this.Setter = setter; + } + + protected readonly IJournalBehaviorSetter Setter; + + protected void SetInterceptor(IJournalInterceptor interceptor) => Setter.SetInterceptor(interceptor); + + public void Pass() => SetInterceptor(JournalInterceptors.Noop.Instance); + + public void PassWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + SetInterceptor(new JournalInterceptors.Delay(delay, JournalInterceptors.Noop.Instance)); + } + + public void Fail() => SetInterceptor(JournalInterceptors.Failure.Instance); + + public void FailOnType() => FailOnType(typeof(TMessage)); + + public void FailOnType(Type messageType) + { + if (messageType is null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + SetInterceptor(new JournalInterceptors.OnType(messageType, JournalInterceptors.Failure.Instance)); + } + + public void FailIf(Func predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); + } + + public void FailIf(Func> predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); + } + + public void FailUnless(Func predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); + } + + public void FailUnless(Func> predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); + } + + public void FailWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + SetInterceptor(new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance)); + } + + public void FailIfWithDelay(TimeSpan delay, Func> predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition( + predicate, + new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance) + )); + } + + public void FailIfWithDelay(TimeSpan delay, Func predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition( + predicate, + new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance) + )); + } + + public void FailUnlessWithDelay(TimeSpan delay, Func predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition( + predicate, + new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance), + negate: true + )); + } + + public void FailUnlessWithDelay(TimeSpan delay, Func> predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + SetInterceptor(new JournalInterceptors.OnCondition( + predicate, + new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance), + negate: true + )); + } + + public void FailOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + + public void FailOnTypeWithDelay(TimeSpan delay, Type messageType) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (messageType is null) + { + throw new ArgumentNullException(nameof(messageType)); + } + + SetInterceptor(new JournalInterceptors.OnType( + messageType, + new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance) + )); + } + } +} diff --git a/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs b/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs index 87810ab6215..c20ee1f0f9f 100644 --- a/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs @@ -9,194 +9,12 @@ namespace Akka.Persistence.TestKit { using System; using System.Threading.Tasks; - using Actor; - public sealed class JournalWriteBehavior + public class JournalWriteBehavior : JournalRecoveryBehavior { - internal JournalWriteBehavior(IActorRef journal) - { - this.journal = journal; - } - - private readonly IActorRef journal; - - private void SetInterceptor(IJournalWriteInterceptor interceptor) - => journal.Ask( - new TestJournal.UseWriteInterceptor(interceptor), - TimeSpan.FromSeconds(3) - ).Wait(); - - public void Pass() => SetInterceptor(JournalWriteInterceptors.Noop.Instance); - - public void PassWithDelay(TimeSpan delay) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - SetInterceptor(new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Noop.Instance)); - } - - public void Fail() => SetInterceptor(JournalWriteInterceptors.Failure.Instance); - - public void FailOnType() => FailOnType(typeof(TMessage)); - - public void FailOnType(Type messageType) - { - if (messageType is null) - { - throw new ArgumentNullException(nameof(messageType)); - } - - SetInterceptor(new JournalWriteInterceptors.OnType(messageType, JournalWriteInterceptors.Failure.Instance)); - } - - public void FailIf(Func predicate) - { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance)); - } - - public void FailIf(Func> predicate) - { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance)); - } - - public void FailUnless(Func predicate) - { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance, negate: true)); - } - - public void FailUnless(Func> predicate) - { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Failure.Instance, negate: true)); - } - - public void FailWithDelay(TimeSpan delay) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - SetInterceptor(new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance)); - } - - public void FailIfWithDelay(TimeSpan delay, Func> predicate) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition( - predicate, - new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance) - )); - } - - public void FailIfWithDelay(TimeSpan delay, Func predicate) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition( - predicate, - new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance) - )); - } - - public void FailUnlessWithDelay(TimeSpan delay, Func predicate) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition( - predicate, - new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance), - negate: true - )); - } - - public void FailUnlessWithDelay(TimeSpan delay, Func> predicate) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - SetInterceptor(new JournalWriteInterceptors.OnCondition( - predicate, - new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance), - negate: true - )); - } - - public void FailOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); - - public void FailOnTypeWithDelay(TimeSpan delay, Type messageType) - { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (messageType is null) - { - throw new ArgumentNullException(nameof(messageType)); - } - - SetInterceptor(new JournalWriteInterceptors.OnType( - messageType, - new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Failure.Instance) - )); - } + internal JournalWriteBehavior(IJournalBehaviorSetter setter) : base(setter) { } - public void Reject() => SetInterceptor(JournalWriteInterceptors.Rejection.Instance); + public void Reject() => SetInterceptor(JournalInterceptors.Rejection.Instance); public void RejectOnType() => FailOnType(typeof(TMessage)); @@ -207,7 +25,7 @@ public void RejectOnType(Type messageType) throw new ArgumentNullException(nameof(messageType)); } - SetInterceptor(new JournalWriteInterceptors.OnType(messageType, JournalWriteInterceptors.Rejection.Instance)); + SetInterceptor(new JournalInterceptors.OnType(messageType, JournalInterceptors.Rejection.Instance)); } public void RejectIf(Func predicate) @@ -217,7 +35,7 @@ public void RejectIf(Func predicate) throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance)); + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); } public void RejectIf(Func> predicate) @@ -227,7 +45,7 @@ public void RejectIf(Func> predicate) throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance)); + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); } public void RejectUnless(Func predicate) @@ -237,7 +55,7 @@ public void RejectUnless(Func predicate) throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance, negate: true)); + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); } public void RejectUnless(Func> predicate) @@ -247,7 +65,7 @@ public void RejectUnless(Func> predicate) throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalWriteInterceptors.OnCondition(predicate, JournalWriteInterceptors.Rejection.Instance, negate: true)); + SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); } public void RejectWithDelay(TimeSpan delay) @@ -257,7 +75,7 @@ public void RejectWithDelay(TimeSpan delay) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); } - SetInterceptor(new JournalWriteInterceptors.Delay(delay, JournalWriteInterceptors.Rejection.Instance)); + SetInterceptor(new JournalInterceptors.Delay(delay, JournalInterceptors.Rejection.Instance)); } public void RejectIfWithDelay(TimeSpan delay, Func> predicate) @@ -272,9 +90,9 @@ public void RejectIfWithDelay(TimeSpan delay, Func> WriteMessagesAsync(IEnu } catch (TestJournalRejectionException rejected) { + // i.e. problems with data: corrupted data-set, problems in serialization, constraints, etc. exceptions.Add(rejected); } catch (TestJournalFailureException) { + // i.e. data-store problems: network, invalid credentials, etc. throw; } } @@ -65,24 +68,52 @@ protected override async Task> WriteMessagesAsync(IEnu return exceptions.ToImmutableList(); } - public override Task ReplayMessagesAsync(IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, Action recoveryCallback) + public override async Task ReplayMessagesAsync(IActorContext context, string persistenceId, long fromSequenceNr, long toSequenceNr, long max, Action recoveryCallback) { - return base.ReplayMessagesAsync(context, persistenceId, fromSequenceNr, toSequenceNr, max, recoveryCallback); + var highest = HighestSequenceNr(persistenceId); + if (highest != 0L && max != 0L) + { + var messages = Read(persistenceId, fromSequenceNr, Math.Min(toSequenceNr, highest), max); + foreach (var p in messages) + { + try + { + await _recoveryInterceptor.InterceptAsync(p); + recoveryCallback(p); + } + catch (TestJournalFailureException) + { + // i.e. problems with data: corrupted data-set, problems in serialization + // i.e. data-store problems: network, invalid credentials, etc. + throw; + } + } + } } public static ITestJournal FromRef(IActorRef actor) { - return new TestJournalWrapper(actor); + return new TestJournalWrapper(actor); } public sealed class UseWriteInterceptor { - public UseWriteInterceptor(IJournalWriteInterceptor interceptor) + public UseWriteInterceptor(IJournalInterceptor interceptor) + { + Interceptor = interceptor; + } + + public IJournalInterceptor Interceptor { get; } + } + + public sealed class UseRecoveryInterceptor + { + public UseRecoveryInterceptor(IJournalInterceptor interceptor) { Interceptor = interceptor; } - public IJournalWriteInterceptor Interceptor { get; } + public IJournalInterceptor Interceptor { get; } } public sealed class Ack @@ -99,7 +130,9 @@ public TestJournalWrapper(IActorRef actor) private readonly IActorRef _actor; - public JournalWriteBehavior OnWrite => new JournalWriteBehavior(_actor); + public JournalWriteBehavior OnWrite => new JournalWriteBehavior(new JournalWriteBehaviorSetter(_actor)); + + public JournalRecoveryBehavior OnRecovery => new JournalRecoveryBehavior(new JournalRecoveryBehaviorSetter(_actor)); } } } From 9dbb8ed9ac4ff7f2511907a6b0c46abf592d92cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sun, 11 Aug 2019 00:08:56 +0300 Subject: [PATCH 04/16] Recovery unit tests --- .../TestJournalSpec.cs | 28 ++++++++++++++----- .../IJournalBehaviorSetter.cs | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs index b76f84c478c..0debdf3c467 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -1,6 +1,7 @@ namespace Akka.Persistence.TestKit.Tests { using System; + using System.Threading.Tasks; using Actor; using Akka.Persistence.TestKit; using Akka.TestKit; @@ -13,7 +14,10 @@ public class TestJournalSpec : AkkaSpec public TestJournalSpec(ITestOutputHelper output = null) : base(GetConfig().ToString(), output) { + _journal = TestJournal.FromRef(GetJournalRef(Sys)); } + + private readonly ITestJournal _journal; [Fact] public void must_return_ack_after_new_write_interceptor_is_set() @@ -28,12 +32,10 @@ public void must_return_ack_after_new_write_interceptor_is_set() [Fact] public void works_as_memory_journal_by_default() { - var journal = TestJournal.FromRef(GetJournalRef(Sys)); - var actor = Sys.ActorOf(); // should pass - journal.OnWrite.Pass(); + _journal.OnWrite.Pass(); actor.Tell("write", TestActor); ExpectMsg("ack", TimeSpan.FromSeconds(3)); } @@ -41,11 +43,10 @@ public void works_as_memory_journal_by_default() [Fact] public void when_fail_on_write_is_set_all_writes_to_journal_will_fail() { - var journal = TestJournal.FromRef(GetJournalRef(Sys)); var actor = Sys.ActorOf(); Watch(actor); - journal.OnWrite.Fail(); + _journal.OnWrite.Fail(); actor.Tell("write", TestActor); ExpectTerminated(actor, TimeSpan.FromSeconds(3)); @@ -54,16 +55,29 @@ public void when_fail_on_write_is_set_all_writes_to_journal_will_fail() [Fact] public void when_reject_on_write_is_set_all_writes_to_journal_will_be_rejected() { - var journal = TestJournal.FromRef(GetJournalRef(Sys)); var actor = Sys.ActorOf(); Watch(actor); - journal.OnWrite.Reject(); + _journal.OnWrite.Reject(); actor.Tell("write", TestActor); ExpectMsg("rejected", TimeSpan.FromSeconds(3)); } + [Fact] + public async Task during_recovery_by_setting_fail_will_cause_recovery_failure() + { + var actor = Sys.ActorOf(); + await actor.Ask("write"); + await actor.GracefulStop(TimeSpan.FromSeconds(3)); + + _journal.OnRecovery.Fail(); + actor = Sys.ActorOf(); + Watch(actor); + + ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + } + static IActorRef GetJournalRef(ActorSystem sys) => Persistence.Instance.Apply(sys).JournalFor(null); static Config GetConfig() diff --git a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs index e3e797adcaf..87ee986b14d 100644 --- a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs @@ -42,7 +42,7 @@ internal JournalRecoveryBehaviorSetter(IActorRef journal) public void SetInterceptor(IJournalInterceptor interceptor) => _journal.Ask( - new TestJournal.UseWriteInterceptor(interceptor), + new TestJournal.UseRecoveryInterceptor(interceptor), TimeSpan.FromSeconds(3) ).Wait(); } From eaa701cc8accdf0197cb1236763b4104a2588da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sun, 11 Aug 2019 14:59:28 +0300 Subject: [PATCH 05/16] PersistenceTestKit base for easier test setup --- .../Akka.Persistence.TestKit.Tests.csproj | 4 --- .../TestJournalSpec.cs | 31 ++++------------ .../Akka.Persistence.TestKit.csproj | 10 ++++++ .../PersistenceTestKit.cs | 35 +++++++++++++++++++ .../test-journal.conf | 0 5 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs rename src/core/{Akka.Persistence.TestKit.Tests => Akka.Persistence.TestKit}/test-journal.conf (100%) diff --git a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj index 05ef7dd9692..c2396b0160f 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj +++ b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj @@ -21,10 +21,6 @@ - - - - $(DefineConstants);CORECLR diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs index 0debdf3c467..2240de75e30 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -4,27 +4,14 @@ namespace Akka.Persistence.TestKit.Tests using System.Threading.Tasks; using Actor; using Akka.Persistence.TestKit; - using Akka.TestKit; - using Configuration; using Xunit; - using Xunit.Abstractions; - public class TestJournalSpec : AkkaSpec + public class TestJournalSpec : PersistenceTestKit { - public TestJournalSpec(ITestOutputHelper output = null) - : base(GetConfig().ToString(), output) - { - _journal = TestJournal.FromRef(GetJournalRef(Sys)); - } - - private readonly ITestJournal _journal; - [Fact] public void must_return_ack_after_new_write_interceptor_is_set() { - var journalActor = GetJournalRef(Sys); - - journalActor.Tell(new TestJournal.UseWriteInterceptor(null), TestActor); + JournalActorRef.Tell(new TestJournal.UseWriteInterceptor(null), TestActor); ExpectMsg(TimeSpan.FromSeconds(3)); } @@ -35,8 +22,9 @@ public void works_as_memory_journal_by_default() var actor = Sys.ActorOf(); // should pass - _journal.OnWrite.Pass(); + Journal.OnWrite.Pass(); actor.Tell("write", TestActor); + ExpectMsg("ack", TimeSpan.FromSeconds(3)); } @@ -46,7 +34,7 @@ public void when_fail_on_write_is_set_all_writes_to_journal_will_fail() var actor = Sys.ActorOf(); Watch(actor); - _journal.OnWrite.Fail(); + Journal.OnWrite.Fail(); actor.Tell("write", TestActor); ExpectTerminated(actor, TimeSpan.FromSeconds(3)); @@ -58,7 +46,7 @@ public void when_reject_on_write_is_set_all_writes_to_journal_will_be_rejected() var actor = Sys.ActorOf(); Watch(actor); - _journal.OnWrite.Reject(); + Journal.OnWrite.Reject(); actor.Tell("write", TestActor); ExpectMsg("rejected", TimeSpan.FromSeconds(3)); @@ -71,17 +59,12 @@ public async Task during_recovery_by_setting_fail_will_cause_recovery_failure() await actor.Ask("write"); await actor.GracefulStop(TimeSpan.FromSeconds(3)); - _journal.OnRecovery.Fail(); + Journal.OnRecovery.Fail(); actor = Sys.ActorOf(); Watch(actor); ExpectTerminated(actor, TimeSpan.FromSeconds(3)); } - - static IActorRef GetJournalRef(ActorSystem sys) => Persistence.Instance.Apply(sys).JournalFor(null); - - static Config GetConfig() - => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.Tests.test-journal.conf"); } public class PersistActor : UntypedPersistentActor diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj index 5b3aa096318..269674ea354 100644 --- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj @@ -11,7 +11,17 @@ + + + + + + + + + + diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs new file mode 100644 index 00000000000..1d4b0d1896a --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using Actor; + using Akka.TestKit; + using Akka.TestKit.Xunit; + using Configuration; + + public abstract class PersistenceTestKit : TestKitBase + { + protected PersistenceTestKit(string actorSystemName = null, string testActorName = null) + : base(new XunitAssertions(), GetConfig(), actorSystemName, testActorName) + { + JournalActorRef = GetJournalRef(Sys); + Journal = TestJournal.FromRef(JournalActorRef); + } + + public IActorRef JournalActorRef { get; } + + public ITestJournal Journal { get; } + + static IActorRef GetJournalRef(ActorSystem sys) + => Persistence.Instance.Apply(sys).JournalFor(null); + + static Config GetConfig() + => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.test-journal.conf"); + + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit.Tests/test-journal.conf b/src/core/Akka.Persistence.TestKit/test-journal.conf similarity index 100% rename from src/core/Akka.Persistence.TestKit.Tests/test-journal.conf rename to src/core/Akka.Persistence.TestKit/test-journal.conf From 8e0f81e51cf00db16fc69896809c89a2e3bdde45 Mon Sep 17 00:00:00 2001 From: Aaron Stannard Date: Mon, 12 Aug 2019 11:45:53 -0500 Subject: [PATCH 06/16] added example of built-in TestKit method --- .../TestJournalSpec.cs | 11 ++++--- .../PersistenceTestKit.cs | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs index 2240de75e30..667694c7406 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -59,11 +59,14 @@ public async Task during_recovery_by_setting_fail_will_cause_recovery_failure() await actor.Ask("write"); await actor.GracefulStop(TimeSpan.FromSeconds(3)); - Journal.OnRecovery.Fail(); - actor = Sys.ActorOf(); - Watch(actor); + WithFailingJournalRecovery(() => + { + actor = Sys.ActorOf(); + Watch(actor); + + ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + }); - ExpectTerminated(actor, TimeSpan.FromSeconds(3)); } } diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs index 1d4b0d1896a..96e59b6f8b2 100644 --- a/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs @@ -11,6 +11,7 @@ namespace Akka.Persistence.TestKit using Akka.TestKit; using Akka.TestKit.Xunit; using Configuration; + using System; public abstract class PersistenceTestKit : TestKitBase { @@ -25,6 +26,34 @@ protected PersistenceTestKit(string actorSystemName = null, string testActorName public ITestJournal Journal { get; } + public void WithFailingJournalRecovery(Action execution) + { + try + { + Journal.OnRecovery.Fail(); + execution(); + } + finally + { + // restore normal functionality + Journal.OnRecovery.Pass(); + } + } + + public void WithFailingJournalWrites(Action execution) + { + try + { + Journal.OnWrite.Fail(); + execution(); + } + finally + { + // restore normal functionality + Journal.OnWrite.Pass(); + } + } + static IActorRef GetJournalRef(ActorSystem sys) => Persistence.Instance.Apply(sys).JournalFor(null); From e32dfff5ae8777f71539105ec2add8f92968f1e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Tue, 13 Aug 2019 12:52:43 +0300 Subject: [PATCH 07/16] use Async api whenver possible --- .../TestJournalSpec.cs | 24 +++- .../Akka.Persistence.TestKit.csproj | 6 +- .../IJournalBehaviorSetter.cs | 15 +-- .../JournalRecoveryBehavior.cs | 58 +++++----- .../JournalWriteBehavior.cs | 50 ++++----- .../PersistenceTestKit.cs | 51 +-------- .../PersistenceTestKitBase.cs | 103 ++++++++++++++++++ 7 files changed, 187 insertions(+), 120 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs index 667694c7406..5fcf2d3a49a 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -17,36 +17,36 @@ public void must_return_ack_after_new_write_interceptor_is_set() } [Fact] - public void works_as_memory_journal_by_default() + public async Task works_as_memory_journal_by_default() { var actor = Sys.ActorOf(); // should pass - Journal.OnWrite.Pass(); + await Journal.OnWrite.Pass(); actor.Tell("write", TestActor); ExpectMsg("ack", TimeSpan.FromSeconds(3)); } [Fact] - public void when_fail_on_write_is_set_all_writes_to_journal_will_fail() + public async Task when_fail_on_write_is_set_all_writes_to_journal_will_fail() { var actor = Sys.ActorOf(); Watch(actor); - Journal.OnWrite.Fail(); + await Journal.OnWrite.Fail(); actor.Tell("write", TestActor); ExpectTerminated(actor, TimeSpan.FromSeconds(3)); } [Fact] - public void when_reject_on_write_is_set_all_writes_to_journal_will_be_rejected() + public async Task when_reject_on_write_is_set_all_writes_to_journal_will_be_rejected() { var actor = Sys.ActorOf(); Watch(actor); - Journal.OnWrite.Reject(); + await Journal.OnWrite.Reject(); actor.Tell("write", TestActor); ExpectMsg("rejected", TimeSpan.FromSeconds(3)); @@ -66,7 +66,19 @@ public async Task during_recovery_by_setting_fail_will_cause_recovery_failure() ExpectTerminated(actor, TimeSpan.FromSeconds(3)); }); + } + [Fact] + public async Task new_api() + { + await WithJournalWrite(write => write.Fail(), () => + { + var actor = Sys.ActorOf(); + Watch(actor); + + actor.Tell("write", TestActor); + ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + }); } } diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj index 269674ea354..f035ac65991 100644 --- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj @@ -10,16 +10,12 @@ 1.6.1 - - - - - + diff --git a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs index 87ee986b14d..75bb2bac0f5 100644 --- a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs @@ -8,11 +8,12 @@ namespace Akka.Persistence.TestKit { using System; + using System.Threading.Tasks; using Actor; public interface IJournalBehaviorSetter { - void SetInterceptor(IJournalInterceptor interceptor); + Task SetInterceptorAsync(IJournalInterceptor interceptor); } internal class JournalWriteBehaviorSetter : IJournalBehaviorSetter @@ -24,11 +25,11 @@ internal JournalWriteBehaviorSetter(IActorRef journal) private readonly IActorRef _journal; - public void SetInterceptor(IJournalInterceptor interceptor) - => _journal.Ask( + public Task SetInterceptorAsync(IJournalInterceptor interceptor) + => _journal.Ask( new TestJournal.UseWriteInterceptor(interceptor), TimeSpan.FromSeconds(3) - ).Wait(); + ); } internal class JournalRecoveryBehaviorSetter : IJournalBehaviorSetter @@ -40,10 +41,10 @@ internal JournalRecoveryBehaviorSetter(IActorRef journal) private readonly IActorRef _journal; - public void SetInterceptor(IJournalInterceptor interceptor) - => _journal.Ask( + public Task SetInterceptorAsync(IJournalInterceptor interceptor) + => _journal.Ask( new TestJournal.UseRecoveryInterceptor(interceptor), TimeSpan.FromSeconds(3) - ).Wait(); + ); } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs b/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs index 19764219af5..d600289e494 100644 --- a/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs @@ -19,85 +19,85 @@ internal JournalRecoveryBehavior(IJournalBehaviorSetter setter) protected readonly IJournalBehaviorSetter Setter; - protected void SetInterceptor(IJournalInterceptor interceptor) => Setter.SetInterceptor(interceptor); + public Task SetInterceptorAsync(IJournalInterceptor interceptor) => Setter.SetInterceptorAsync(interceptor); - public void Pass() => SetInterceptor(JournalInterceptors.Noop.Instance); + public Task Pass() => SetInterceptorAsync(JournalInterceptors.Noop.Instance); - public void PassWithDelay(TimeSpan delay) + public Task PassWithDelay(TimeSpan delay) { if (delay <= TimeSpan.Zero) { throw new ArgumentException("Delay must be greater than zero", nameof(delay)); } - SetInterceptor(new JournalInterceptors.Delay(delay, JournalInterceptors.Noop.Instance)); + return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Noop.Instance)); } - public void Fail() => SetInterceptor(JournalInterceptors.Failure.Instance); + public Task Fail() => SetInterceptorAsync(JournalInterceptors.Failure.Instance); - public void FailOnType() => FailOnType(typeof(TMessage)); + public Task FailOnType() => FailOnType(typeof(TMessage)); - public void FailOnType(Type messageType) + public Task FailOnType(Type messageType) { if (messageType is null) { throw new ArgumentNullException(nameof(messageType)); } - SetInterceptor(new JournalInterceptors.OnType(messageType, JournalInterceptors.Failure.Instance)); + return SetInterceptorAsync(new JournalInterceptors.OnType(messageType, JournalInterceptors.Failure.Instance)); } - public void FailIf(Func predicate) + public Task FailIf(Func predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); } - public void FailIf(Func> predicate) + public Task FailIf(Func> predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); } - public void FailUnless(Func predicate) + public Task FailUnless(Func predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); } - public void FailUnless(Func> predicate) + public Task FailUnless(Func> predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); } - public void FailWithDelay(TimeSpan delay) + public Task FailWithDelay(TimeSpan delay) { if (delay <= TimeSpan.Zero) { throw new ArgumentException("Delay must be greater than zero", nameof(delay)); } - SetInterceptor(new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance)); + return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance)); } - public void FailIfWithDelay(TimeSpan delay, Func> predicate) + public Task FailIfWithDelay(TimeSpan delay, Func> predicate) { if (delay <= TimeSpan.Zero) { @@ -109,13 +109,13 @@ public void FailIfWithDelay(TimeSpan delay, Func predicate) + public Task FailIfWithDelay(TimeSpan delay, Func predicate) { if (delay <= TimeSpan.Zero) { @@ -127,13 +127,13 @@ public void FailIfWithDelay(TimeSpan delay, Func predicate) + public Task FailUnlessWithDelay(TimeSpan delay, Func predicate) { if (delay <= TimeSpan.Zero) { @@ -145,14 +145,14 @@ public void FailUnlessWithDelay(TimeSpan delay, Func> predicate) + public Task FailUnlessWithDelay(TimeSpan delay, Func> predicate) { if (delay <= TimeSpan.Zero) { @@ -164,16 +164,16 @@ public void FailUnlessWithDelay(TimeSpan delay, Func(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + public Task FailOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); - public void FailOnTypeWithDelay(TimeSpan delay, Type messageType) + public Task FailOnTypeWithDelay(TimeSpan delay, Type messageType) { if (delay <= TimeSpan.Zero) { @@ -185,7 +185,7 @@ public void FailOnTypeWithDelay(TimeSpan delay, Type messageType) throw new ArgumentNullException(nameof(messageType)); } - SetInterceptor(new JournalInterceptors.OnType( + return SetInterceptorAsync(new JournalInterceptors.OnType( messageType, new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance) )); diff --git a/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs b/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs index c20ee1f0f9f..d25a16c399a 100644 --- a/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs @@ -14,71 +14,71 @@ public class JournalWriteBehavior : JournalRecoveryBehavior { internal JournalWriteBehavior(IJournalBehaviorSetter setter) : base(setter) { } - public void Reject() => SetInterceptor(JournalInterceptors.Rejection.Instance); + public Task Reject() => SetInterceptorAsync(JournalInterceptors.Rejection.Instance); - public void RejectOnType() => FailOnType(typeof(TMessage)); + public Task RejectOnType() => FailOnType(typeof(TMessage)); - public void RejectOnType(Type messageType) + public Task RejectOnType(Type messageType) { if (messageType is null) { throw new ArgumentNullException(nameof(messageType)); } - SetInterceptor(new JournalInterceptors.OnType(messageType, JournalInterceptors.Rejection.Instance)); + return SetInterceptorAsync(new JournalInterceptors.OnType(messageType, JournalInterceptors.Rejection.Instance)); } - public void RejectIf(Func predicate) + public Task RejectIf(Func predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); } - public void RejectIf(Func> predicate) + public Task RejectIf(Func> predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); } - public void RejectUnless(Func predicate) + public Task RejectUnless(Func predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); } - public void RejectUnless(Func> predicate) + public Task RejectUnless(Func> predicate) { if (predicate is null) { throw new ArgumentNullException(nameof(predicate)); } - SetInterceptor(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); + return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); } - public void RejectWithDelay(TimeSpan delay) + public Task RejectWithDelay(TimeSpan delay) { if (delay <= TimeSpan.Zero) { throw new ArgumentException("Delay must be greater than zero", nameof(delay)); } - SetInterceptor(new JournalInterceptors.Delay(delay, JournalInterceptors.Rejection.Instance)); + return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Rejection.Instance)); } - public void RejectIfWithDelay(TimeSpan delay, Func> predicate) + public Task RejectIfWithDelay(TimeSpan delay, Func> predicate) { if (delay <= TimeSpan.Zero) { @@ -90,13 +90,13 @@ public void RejectIfWithDelay(TimeSpan delay, Func predicate) + public Task RejectIfWithDelay(TimeSpan delay, Func predicate) { if (delay <= TimeSpan.Zero) { @@ -108,13 +108,13 @@ public void RejectIfWithDelay(TimeSpan delay, Func predicate) + public Task RejectUnlessWithDelay(TimeSpan delay, Func predicate) { if (delay <= TimeSpan.Zero) { @@ -126,14 +126,14 @@ public void RejectUnlessWithDelay(TimeSpan delay, Func> predicate) + public Task RejectUnlessWithDelay(TimeSpan delay, Func> predicate) { if (delay <= TimeSpan.Zero) { @@ -145,16 +145,16 @@ public void RejectUnlessWithDelay(TimeSpan delay, Func(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + public Task RejectOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); - public void RejectOnTypeWithDelay(TimeSpan delay, Type messageType) + public Task RejectOnTypeWithDelay(TimeSpan delay, Type messageType) { if (delay <= TimeSpan.Zero) { @@ -166,7 +166,7 @@ public void RejectOnTypeWithDelay(TimeSpan delay, Type messageType) throw new ArgumentNullException(nameof(messageType)); } - SetInterceptor(new JournalInterceptors.OnType( + return SetInterceptorAsync(new JournalInterceptors.OnType( messageType, new JournalInterceptors.Delay(delay, JournalInterceptors.Rejection.Instance) )); diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs index 96e59b6f8b2..ce467aac644 100644 --- a/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs @@ -7,58 +7,13 @@ namespace Akka.Persistence.TestKit { - using Actor; - using Akka.TestKit; - using Akka.TestKit.Xunit; - using Configuration; - using System; + using Akka.TestKit.Xunit2; - public abstract class PersistenceTestKit : TestKitBase + public abstract class PersistenceTestKit : PersistenceTestKitBase { protected PersistenceTestKit(string actorSystemName = null, string testActorName = null) - : base(new XunitAssertions(), GetConfig(), actorSystemName, testActorName) + : base(new XunitAssertions(), actorSystemName, testActorName) { - JournalActorRef = GetJournalRef(Sys); - Journal = TestJournal.FromRef(JournalActorRef); } - - public IActorRef JournalActorRef { get; } - - public ITestJournal Journal { get; } - - public void WithFailingJournalRecovery(Action execution) - { - try - { - Journal.OnRecovery.Fail(); - execution(); - } - finally - { - // restore normal functionality - Journal.OnRecovery.Pass(); - } - } - - public void WithFailingJournalWrites(Action execution) - { - try - { - Journal.OnWrite.Fail(); - execution(); - } - finally - { - // restore normal functionality - Journal.OnWrite.Pass(); - } - } - - static IActorRef GetJournalRef(ActorSystem sys) - => Persistence.Instance.Apply(sys).JournalFor(null); - - static Config GetConfig() - => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.test-journal.conf"); - } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs new file mode 100644 index 00000000000..1d9d51cadaf --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs @@ -0,0 +1,103 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + using Actor; + using Akka.TestKit; + using Configuration; + + public abstract class PersistenceTestKitBase : TestKitBase + { + protected PersistenceTestKitBase(ITestKitAssertions assertions, string actorSystemName = null, string testActorName = null) + : base(assertions, GetConfig(), actorSystemName, testActorName) + { + JournalActorRef = GetJournalRef(Sys); + Journal = TestJournal.FromRef(JournalActorRef); + } + + public IActorRef JournalActorRef { get; } + + public ITestJournal Journal { get; } + + public async Task WithJournalRecovery(Func behaviorSelector, Func execution) + { + try + { + await behaviorSelector(Journal.OnRecovery); + await execution(); + } + finally + { + await Journal.OnRecovery.Pass(); + } + } + + public async Task WithJournalWrite(Func behaviorSelector, Func execution) + { + try + { + await behaviorSelector(Journal.OnWrite); + await execution(); + } + finally + { + await Journal.OnWrite.Pass(); + } + } + + public Task WithJournalRecovery(Func behaviorSelector, Action execution) + => WithJournalRecovery(behaviorSelector, () => + { + execution(); + return Task.FromResult(new object()); + }); + + public Task WithJournalWrite(Func behaviorSelector, Action execution) + => WithJournalWrite(behaviorSelector, () => + { + execution(); + return Task.FromResult(new object()); + }); + + public void WithFailingJournalRecovery(Action execution) + { + try + { + Journal.OnRecovery.Fail(); + execution(); + } + finally + { + // restore normal functionality + Journal.OnRecovery.Pass(); + } + } + + public void WithFailingJournalWrites(Action execution) + { + try + { + Journal.OnWrite.Fail(); + execution(); + } + finally + { + // restore normal functionality + Journal.OnWrite.Pass(); + } + } + + static IActorRef GetJournalRef(ActorSystem sys) + => Persistence.Instance.Apply(sys).JournalFor(null); + + static Config GetConfig() + => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.test-journal.conf"); + } +} \ No newline at end of file From aceb9aa2c36de4bd9199d6b0dcd407722111137b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Wed, 14 Aug 2019 09:27:06 +0300 Subject: [PATCH 08/16] (wip) TestSnapshotStore --- .../Journal/IJournalBehaviorSetter.cs | 16 +++ .../{ => Journal}/IJournalInterceptor.cs | 0 .../{ => Journal}/ITestJournal.cs | 0 .../{ => Journal}/JournalInterceptors.cs | 0 .../{ => Journal}/JournalRecoveryBehavior.cs | 0 .../JournalRecoveryBehaviorSetter.cs} | 23 +--- .../{ => Journal}/JournalWriteBehavior.cs | 0 .../Journal/JournalWriteBehaviorSetter.cs | 29 +++++ .../{ => Journal}/TestJournal.cs | 0 .../TestJournalFailureException.cs | 0 .../ISnapshotStoreBehaviorSetter.cs | 16 +++ .../ISnapshotStoreInterceptor.cs | 16 +++ .../SnapshotStore/ITestSnapshotStore.cs | 16 +++ .../SnapshotStoreDeleteBehavior.cs | 17 +++ .../SnapshotStoreDeleteBehaviorSetter.cs | 29 +++++ .../SnapshotStoreInterceptors.cs | 22 ++++ .../SnapshotStoreLoadBehavior.cs | 17 +++ .../SnapshotStoreLoadBehaviorSetter.cs | 29 +++++ .../SnapshotStoreSaveBehavior.cs | 19 +++ .../SnapshotStoreSaveBehaviorSetter.cs | 29 +++++ .../SnapshotStore/TestSnapshotStore.cs | 116 ++++++++++++++++++ .../TestSnapshotStoreFailureException.cs | 21 ++++ 22 files changed, 393 insertions(+), 22 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit/Journal/IJournalBehaviorSetter.cs rename src/core/Akka.Persistence.TestKit/{ => Journal}/IJournalInterceptor.cs (100%) rename src/core/Akka.Persistence.TestKit/{ => Journal}/ITestJournal.cs (100%) rename src/core/Akka.Persistence.TestKit/{ => Journal}/JournalInterceptors.cs (100%) rename src/core/Akka.Persistence.TestKit/{ => Journal}/JournalRecoveryBehavior.cs (100%) rename src/core/Akka.Persistence.TestKit/{IJournalBehaviorSetter.cs => Journal/JournalRecoveryBehaviorSetter.cs} (57%) rename src/core/Akka.Persistence.TestKit/{ => Journal}/JournalWriteBehavior.cs (100%) create mode 100644 src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs rename src/core/Akka.Persistence.TestKit/{ => Journal}/TestJournal.cs (100%) rename src/core/Akka.Persistence.TestKit/{ => Journal}/TestJournalFailureException.cs (100%) create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreBehaviorSetter.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/ITestSnapshotStore.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehavior.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehavior.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs create mode 100644 src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStoreFailureException.cs diff --git a/src/core/Akka.Persistence.TestKit/Journal/IJournalBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/Journal/IJournalBehaviorSetter.cs new file mode 100644 index 00000000000..49cd75668bb --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/Journal/IJournalBehaviorSetter.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System.Threading.Tasks; + + public interface IJournalBehaviorSetter + { + Task SetInterceptorAsync(IJournalInterceptor interceptor); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/IJournalInterceptor.cs b/src/core/Akka.Persistence.TestKit/Journal/IJournalInterceptor.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/IJournalInterceptor.cs rename to src/core/Akka.Persistence.TestKit/Journal/IJournalInterceptor.cs diff --git a/src/core/Akka.Persistence.TestKit/ITestJournal.cs b/src/core/Akka.Persistence.TestKit/Journal/ITestJournal.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/ITestJournal.cs rename to src/core/Akka.Persistence.TestKit/Journal/ITestJournal.cs diff --git a/src/core/Akka.Persistence.TestKit/JournalInterceptors.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalInterceptors.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/JournalInterceptors.cs rename to src/core/Akka.Persistence.TestKit/Journal/JournalInterceptors.cs diff --git a/src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/JournalRecoveryBehavior.cs rename to src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs diff --git a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs similarity index 57% rename from src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs rename to src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs index 75bb2bac0f5..d184d364334 100644 --- a/src/core/Akka.Persistence.TestKit/IJournalBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs @@ -1,5 +1,5 @@ //----------------------------------------------------------------------- -// +// // Copyright (C) 2009-2019 Lightbend Inc. // Copyright (C) 2013-2019 .NET Foundation // @@ -11,27 +11,6 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; using Actor; - public interface IJournalBehaviorSetter - { - Task SetInterceptorAsync(IJournalInterceptor interceptor); - } - - internal class JournalWriteBehaviorSetter : IJournalBehaviorSetter - { - internal JournalWriteBehaviorSetter(IActorRef journal) - { - this._journal = journal; - } - - private readonly IActorRef _journal; - - public Task SetInterceptorAsync(IJournalInterceptor interceptor) - => _journal.Ask( - new TestJournal.UseWriteInterceptor(interceptor), - TimeSpan.FromSeconds(3) - ); - } - internal class JournalRecoveryBehaviorSetter : IJournalBehaviorSetter { internal JournalRecoveryBehaviorSetter(IActorRef journal) diff --git a/src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehavior.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/JournalWriteBehavior.cs rename to src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehavior.cs diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs new file mode 100644 index 00000000000..fdd54a19409 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs @@ -0,0 +1,29 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + using Actor; + + internal class JournalWriteBehaviorSetter : IJournalBehaviorSetter + { + internal JournalWriteBehaviorSetter(IActorRef journal) + { + this._journal = journal; + } + + private readonly IActorRef _journal; + + public Task SetInterceptorAsync(IJournalInterceptor interceptor) + => _journal.Ask( + new TestJournal.UseWriteInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/TestJournal.cs b/src/core/Akka.Persistence.TestKit/Journal/TestJournal.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/TestJournal.cs rename to src/core/Akka.Persistence.TestKit/Journal/TestJournal.cs diff --git a/src/core/Akka.Persistence.TestKit/TestJournalFailureException.cs b/src/core/Akka.Persistence.TestKit/Journal/TestJournalFailureException.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/TestJournalFailureException.cs rename to src/core/Akka.Persistence.TestKit/Journal/TestJournalFailureException.cs diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreBehaviorSetter.cs new file mode 100644 index 00000000000..e0d475217fb --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreBehaviorSetter.cs @@ -0,0 +1,16 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System.Threading.Tasks; + + public interface ISnapshotStoreBehaviorSetter + { + Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs new file mode 100644 index 00000000000..15807ccff2d --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System.Threading.Tasks; + + public interface ISnapshotStoreInterceptor + { + Task Intercept(string persistenceId, SnapshotSelectionCriteria criteria); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/ITestSnapshotStore.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/ITestSnapshotStore.cs new file mode 100644 index 00000000000..13b5d67e267 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/ITestSnapshotStore.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + public interface ITestSnapshotStore + { + SnapshotStoreSaveBehavior OnSave { get; } + SnapshotStoreLoadBehavior OnLoad { get; } + SnapshotStoreDeleteBehavior OnDelete { get; } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehavior.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehavior.cs new file mode 100644 index 00000000000..17c72909102 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehavior.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + public class SnapshotStoreDeleteBehavior : SnapshotStoreSaveBehavior + { + public SnapshotStoreDeleteBehavior(ISnapshotStoreBehaviorSetter setter) : base(setter) + { + + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs new file mode 100644 index 00000000000..89b95bb3152 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + using Actor; + + internal class SnapshotStoreDeleteBehaviorSetter : ISnapshotStoreBehaviorSetter + { + internal SnapshotStoreDeleteBehaviorSetter(IActorRef snapshots) + { + this._snapshots = snapshots; + } + + private readonly IActorRef _snapshots; + + public Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor) + => _snapshots.Ask( + new TestSnapshotStore.UseDeleteInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs new file mode 100644 index 00000000000..45113e0f0fe --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs @@ -0,0 +1,22 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System.Threading.Tasks; + + internal static class SnapshotStoreInterceptors + { + internal class Failure : ISnapshotStoreInterceptor + { + public Task Intercept(string persistenceId, SnapshotSelectionCriteria criteria) + { + throw new TestSnapshotStoreFailureException(); + } + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehavior.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehavior.cs new file mode 100644 index 00000000000..6c363ba7dd1 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehavior.cs @@ -0,0 +1,17 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + public class SnapshotStoreLoadBehavior : SnapshotStoreSaveBehavior + { + public SnapshotStoreLoadBehavior(ISnapshotStoreBehaviorSetter setter) : base(setter) + { + + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs new file mode 100644 index 00000000000..05859d0a228 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + using Actor; + + internal class SnapshotStoreLoadBehaviorSetter : ISnapshotStoreBehaviorSetter + { + internal SnapshotStoreLoadBehaviorSetter(IActorRef snapshots) + { + this._snapshots = snapshots; + } + + private readonly IActorRef _snapshots; + + public Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor) + => _snapshots.Ask( + new TestSnapshotStore.UseLoadInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs new file mode 100644 index 00000000000..2753be600e0 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + public class SnapshotStoreSaveBehavior + { + public SnapshotStoreSaveBehavior(ISnapshotStoreBehaviorSetter setter) + { + Setter = setter; + } + + protected readonly ISnapshotStoreBehaviorSetter Setter; + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs new file mode 100644 index 00000000000..dd7d2e9a6c6 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Threading.Tasks; + using Actor; + + internal class SnapshotStoreSaveBehaviorSetter : ISnapshotStoreBehaviorSetter + { + internal SnapshotStoreSaveBehaviorSetter(IActorRef snapshots) + { + this._snapshots = snapshots; + } + + private readonly IActorRef _snapshots; + + public Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor) + => _snapshots.Ask( + new TestSnapshotStore.UseSaveInterceptor(interceptor), + TimeSpan.FromSeconds(3) + ); + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs new file mode 100644 index 00000000000..8c0432bae1f --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs @@ -0,0 +1,116 @@ +namespace Akka.Persistence.TestKit +{ + using System.Threading.Tasks; + using Actor; + using Snapshot; + + public class TestSnapshotStore : MemorySnapshotStore + { + private ISnapshotStoreInterceptor _saveInterceptor; + private ISnapshotStoreInterceptor _loadInterceptor; + private ISnapshotStoreInterceptor _deleteInterceptor; + + protected override bool ReceivePluginInternal(object message) + { + switch (message) + { + case UseSaveInterceptor use: + _saveInterceptor = use.Interceptor; + return true; + + case UseLoadInterceptor use: + _loadInterceptor = use.Interceptor; + return true; + + case UseDeleteInterceptor use: + _deleteInterceptor = use.Interceptor; + return true; + + default: + return base.ReceivePluginInternal(message); + } + } + + protected override async Task SaveAsync(SnapshotMetadata metadata, object snapshot) + { + await _saveInterceptor.Intercept(metadata.PersistenceId, ToSelectionCriteria(metadata)); + await base.SaveAsync(metadata, snapshot); + } + + protected override async Task LoadAsync(string persistenceId, SnapshotSelectionCriteria criteria) + { + await _loadInterceptor.Intercept(persistenceId, criteria); + return await base.LoadAsync(persistenceId, criteria); + } + + protected override async Task DeleteAsync(SnapshotMetadata metadata) + { + await _deleteInterceptor.Intercept(metadata.PersistenceId, ToSelectionCriteria(metadata)); + await base.DeleteAsync(metadata); + } + + protected override async Task DeleteAsync(string persistenceId, SnapshotSelectionCriteria criteria) + { + await _deleteInterceptor.Intercept(persistenceId, criteria); + await base.DeleteAsync(persistenceId, criteria); + } + + static SnapshotSelectionCriteria ToSelectionCriteria(SnapshotMetadata metadata) + => new SnapshotSelectionCriteria(metadata.SequenceNr, metadata.Timestamp, metadata.SequenceNr, + metadata.Timestamp); + + public static ITestSnapshotStore FromRef(IActorRef actor) + { + return new TestSnapshotStoreWrapper(actor); + } + + public sealed class UseSaveInterceptor + { + public UseSaveInterceptor(ISnapshotStoreInterceptor interceptor) + { + Interceptor = interceptor; + } + + public ISnapshotStoreInterceptor Interceptor { get; } + } + + public sealed class UseLoadInterceptor + { + public UseLoadInterceptor(ISnapshotStoreInterceptor interceptor) + { + Interceptor = interceptor; + } + + public ISnapshotStoreInterceptor Interceptor { get; } + } + + public sealed class UseDeleteInterceptor + { + public UseDeleteInterceptor(ISnapshotStoreInterceptor interceptor) + { + Interceptor = interceptor; + } + + public ISnapshotStoreInterceptor Interceptor { get; } + } + + public sealed class Ack + { + public static readonly Ack Instance = new Ack(); + } + + internal class TestSnapshotStoreWrapper : ITestSnapshotStore + { + public TestSnapshotStoreWrapper(IActorRef actor) + { + _actor = actor; + } + + private readonly IActorRef _actor; + + public SnapshotStoreSaveBehavior OnSave => new SnapshotStoreSaveBehavior(new SnapshotStoreSaveBehaviorSetter(_actor)); + public SnapshotStoreLoadBehavior OnLoad => new SnapshotStoreLoadBehavior(new SnapshotStoreLoadBehaviorSetter(_actor)); + public SnapshotStoreDeleteBehavior OnDelete => new SnapshotStoreDeleteBehavior(new SnapshotStoreDeleteBehaviorSetter(_actor)); + } + } +} diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStoreFailureException.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStoreFailureException.cs new file mode 100644 index 00000000000..a67fc0ffc82 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStoreFailureException.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +//----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class TestSnapshotStoreFailureException : Exception + { + public TestSnapshotStoreFailureException() { } + public TestSnapshotStoreFailureException(string message) : base(message) { } + public TestSnapshotStoreFailureException(string message, Exception inner) : base(message, inner) { } + protected TestSnapshotStoreFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} \ No newline at end of file From 55b9f92bc95bc08f14d9968af51978a2e0eec554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Wed, 14 Aug 2019 19:35:24 +0300 Subject: [PATCH 09/16] TestSnapshotStore interceptors --- .../PersistenceTestKitBase.cs | 66 ++++++-- .../ISnapshotStoreInterceptor.cs | 2 +- .../SnapshotStoreInterceptors.cs | 59 ++++++- .../SnapshotStoreSaveBehavior.cs | 146 ++++++++++++++++++ .../SnapshotStore/TestSnapshotStore.cs | 8 +- 5 files changed, 260 insertions(+), 21 deletions(-) diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs index 1d9d51cadaf..0cd5700ff6d 100644 --- a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs @@ -18,13 +18,22 @@ public abstract class PersistenceTestKitBase : TestKitBase protected PersistenceTestKitBase(ITestKitAssertions assertions, string actorSystemName = null, string testActorName = null) : base(assertions, GetConfig(), actorSystemName, testActorName) { - JournalActorRef = GetJournalRef(Sys); + _persistenceExtension = Persistence.Instance.Apply(Sys); + + JournalActorRef = _persistenceExtension.JournalFor(null); Journal = TestJournal.FromRef(JournalActorRef); + + SnapshotsActorRef = _persistenceExtension.SnapshotStoreFor(null); + Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef); } + private readonly PersistenceExtension _persistenceExtension; + public IActorRef JournalActorRef { get; } + public IActorRef SnapshotsActorRef { get; } public ITestJournal Journal { get; } + public ITestSnapshotStore Snapshots { get; } public async Task WithJournalRecovery(Func behaviorSelector, Func execution) { @@ -66,37 +75,66 @@ public Task WithJournalWrite(Func behaviorSelector, return Task.FromResult(new object()); }); - public void WithFailingJournalRecovery(Action execution) + public async Task WithSnapshotSave(Func behaviorSelector, Func execution) { try { - Journal.OnRecovery.Fail(); - execution(); + await behaviorSelector(Snapshots.OnSave); + await execution(); } finally { - // restore normal functionality - Journal.OnRecovery.Pass(); + await Snapshots.OnSave.Pass(); } } - public void WithFailingJournalWrites(Action execution) + public async Task WithSnapshotLoad(Func behaviorSelector, Func execution) { try { - Journal.OnWrite.Fail(); - execution(); + await behaviorSelector(Snapshots.OnLoad); + await execution(); + } + finally + { + await Snapshots.OnLoad.Pass(); + } + } + + public async Task WithSnapshotDelete(Func behaviorSelector, Func execution) + { + try + { + await behaviorSelector(Snapshots.OnDelete); + await execution(); } finally { - // restore normal functionality - Journal.OnWrite.Pass(); + await Snapshots.OnDelete.Pass(); } - } + } + + public Task WithSnapshotSave(Func behaviorSelector, Action execution) + => WithSnapshotSave(behaviorSelector, () => + { + execution(); + return Task.FromResult(true); + }); - static IActorRef GetJournalRef(ActorSystem sys) - => Persistence.Instance.Apply(sys).JournalFor(null); + public Task WithSnapshotLoad(Func behaviorSelector, Action execution) + => WithSnapshotLoad(behaviorSelector, () => + { + execution(); + return Task.FromResult(true); + }); + public Task WithSnapshotDelete(Func behaviorSelector, Action execution) + => WithSnapshotDelete(behaviorSelector, () => + { + execution(); + return Task.FromResult(true); + }); + static Config GetConfig() => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.test-journal.conf"); } diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs index 15807ccff2d..98b7be6665d 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs @@ -11,6 +11,6 @@ namespace Akka.Persistence.TestKit public interface ISnapshotStoreInterceptor { - Task Intercept(string persistenceId, SnapshotSelectionCriteria criteria); + Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria); } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs index 45113e0f0fe..f9fcfafe71b 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreInterceptors.cs @@ -7,15 +7,70 @@ namespace Akka.Persistence.TestKit { + using System; using System.Threading.Tasks; internal static class SnapshotStoreInterceptors { + internal class Noop : ISnapshotStoreInterceptor + { + public static readonly ISnapshotStoreInterceptor Instance = new Noop(); + + public Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria) => Task.FromResult(true); + } + internal class Failure : ISnapshotStoreInterceptor { - public Task Intercept(string persistenceId, SnapshotSelectionCriteria criteria) + public static readonly ISnapshotStoreInterceptor Instance = new Failure(); + + public Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria) => throw new TestSnapshotStoreFailureException(); + } + + internal class Delay : ISnapshotStoreInterceptor + { + public Delay(TimeSpan delay, ISnapshotStoreInterceptor next) + { + _delay = delay; + _next = next; + } + + private readonly TimeSpan _delay; + private readonly ISnapshotStoreInterceptor _next; + + public async Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria) + { + await Task.Delay(_delay); + await _next.InterceptAsync(persistenceId, criteria); + } + } + + internal sealed class OnCondition : ISnapshotStoreInterceptor + { + public OnCondition(Func> predicate, ISnapshotStoreInterceptor next, bool negate = false) + { + _predicate = predicate; + _next = next; + _negate = negate; + } + + public OnCondition(Func predicate, ISnapshotStoreInterceptor next, bool negate = false) + { + _predicate = (persistenceId, criteria) => Task.FromResult(predicate(persistenceId, criteria)); + _next = next; + _negate = negate; + } + + private readonly Func> _predicate; + private readonly ISnapshotStoreInterceptor _next; + private readonly bool _negate; + + public async Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria) { - throw new TestSnapshotStoreFailureException(); + var result = await _predicate(persistenceId, criteria); + if ((_negate && !result) || (!_negate && result)) + { + await _next.InterceptAsync(persistenceId, criteria); + } } } } diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs index 2753be600e0..a4b467a8349 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs @@ -7,6 +7,12 @@ namespace Akka.Persistence.TestKit { + using System; + using System.Threading.Tasks; + + using InterceptorPredicate = System.Func; + using AsyncInterceptorPredicate = System.Func>; + public class SnapshotStoreSaveBehavior { public SnapshotStoreSaveBehavior(ISnapshotStoreBehaviorSetter setter) @@ -15,5 +21,145 @@ public SnapshotStoreSaveBehavior(ISnapshotStoreBehaviorSetter setter) } protected readonly ISnapshotStoreBehaviorSetter Setter; + + public Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor) => Setter.SetInterceptorAsync(interceptor); + + public Task Pass() => SetInterceptorAsync(SnapshotStoreInterceptors.Noop.Instance); + + public Task PassWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Noop.Instance)); + } + + public Task Fail() => SetInterceptorAsync(SnapshotStoreInterceptors.Failure.Instance); + + public Task FailIf(InterceptorPredicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance)); + } + + public Task FailIf(AsyncInterceptorPredicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance)); + } + + public Task FailUnless(InterceptorPredicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance, negate: true)); + } + + public Task FailUnless(AsyncInterceptorPredicate predicate) + { + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance, negate: true)); + } + + public Task FailWithDelay(TimeSpan delay) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Failure.Instance)); + } + + public Task FailIfWithDelay(TimeSpan delay, AsyncInterceptorPredicate predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( + predicate, + new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Failure.Instance) + )); + } + + public Task FailIfWithDelay(TimeSpan delay, InterceptorPredicate predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( + predicate, + new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Failure.Instance) + )); + } + + public Task FailUnlessWithDelay(TimeSpan delay, InterceptorPredicate predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( + predicate, + new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Failure.Instance), + negate: true + )); + } + + public Task FailUnlessWithDelay(TimeSpan delay, AsyncInterceptorPredicate predicate) + { + if (delay <= TimeSpan.Zero) + { + throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + } + + if (predicate is null) + { + throw new ArgumentNullException(nameof(predicate)); + } + + return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( + predicate, + new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Failure.Instance), + negate: true + )); + } } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs index 8c0432bae1f..06c495af206 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs @@ -33,25 +33,25 @@ protected override bool ReceivePluginInternal(object message) protected override async Task SaveAsync(SnapshotMetadata metadata, object snapshot) { - await _saveInterceptor.Intercept(metadata.PersistenceId, ToSelectionCriteria(metadata)); + await _saveInterceptor.InterceptAsync(metadata.PersistenceId, ToSelectionCriteria(metadata)); await base.SaveAsync(metadata, snapshot); } protected override async Task LoadAsync(string persistenceId, SnapshotSelectionCriteria criteria) { - await _loadInterceptor.Intercept(persistenceId, criteria); + await _loadInterceptor.InterceptAsync(persistenceId, criteria); return await base.LoadAsync(persistenceId, criteria); } protected override async Task DeleteAsync(SnapshotMetadata metadata) { - await _deleteInterceptor.Intercept(metadata.PersistenceId, ToSelectionCriteria(metadata)); + await _deleteInterceptor.InterceptAsync(metadata.PersistenceId, ToSelectionCriteria(metadata)); await base.DeleteAsync(metadata); } protected override async Task DeleteAsync(string persistenceId, SnapshotSelectionCriteria criteria) { - await _deleteInterceptor.Intercept(persistenceId, criteria); + await _deleteInterceptor.InterceptAsync(persistenceId, criteria); await base.DeleteAsync(persistenceId, criteria); } From 610999f19e92f719508e1485b07236389b019f07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Fri, 16 Aug 2019 23:53:06 +0300 Subject: [PATCH 10/16] unit tests for TestSnapshotStore --- .../Actors/PersistActor.cs | 47 ++++++++ .../Actors/SnapshotActor.cs | 100 ++++++++++++++++++ ...rsSpecs.cs => JournalInterceptorsSpecs.cs} | 2 +- .../SnapshotStoreInterceptorsSpec.cs | 98 +++++++++++++++++ .../TestJournalSpec.cs | 55 +--------- .../TestSnapshotStoreSpec.cs | 94 ++++++++++++++++ .../SnapshotStore/TestSnapshotStore.cs | 9 +- .../test-journal.conf | 10 ++ 8 files changed, 361 insertions(+), 54 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs create mode 100644 src/core/Akka.Persistence.TestKit.Tests/Actors/SnapshotActor.cs rename src/core/Akka.Persistence.TestKit.Tests/{JournalWriteInterceptorsSpecs.cs => JournalInterceptorsSpecs.cs} (99%) create mode 100644 src/core/Akka.Persistence.TestKit.Tests/SnapshotStoreInterceptorsSpec.cs create mode 100644 src/core/Akka.Persistence.TestKit.Tests/TestSnapshotStoreSpec.cs diff --git a/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs b/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs new file mode 100644 index 00000000000..c5e6b31848d --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs @@ -0,0 +1,47 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit.Tests +{ + using System; + using Actor; + + public class PersistActor : UntypedPersistentActor + { + public override string PersistenceId => "foo"; + + protected override void OnCommand(object message) + { + var sender = Sender; + switch (message as string) + { + case "write": + Persist(message, _ => + { + sender.Tell("ack"); + }); + + break; + + default: + return; + } + } + + protected override void OnRecover(object message) + { + // noop + } + + protected override void OnPersistRejected(Exception cause, object @event, long sequenceNr) + { + Sender.Tell("rejected"); + + base.OnPersistRejected(cause, @event, sequenceNr); + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit.Tests/Actors/SnapshotActor.cs b/src/core/Akka.Persistence.TestKit.Tests/Actors/SnapshotActor.cs new file mode 100644 index 00000000000..75104f4b39b --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/Actors/SnapshotActor.cs @@ -0,0 +1,100 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit.Tests +{ + using System; + using Actor; + + public class SnapshotActor : UntypedPersistentActor + { + public SnapshotActor(IActorRef probe) + { + _probe = probe; + } + + private readonly IActorRef _probe; + + public override string PersistenceId => "bar"; + + protected override void OnCommand(object message) + { + switch (message) + { + case "save": + SaveSnapshot(message); + return; + + case DeleteOne del: + DeleteSnapshot(del.SequenceNr); + return; + + case DeleteMany del: + DeleteSnapshots(del.Criteria); + return; + + case SaveSnapshotSuccess _: + case SaveSnapshotFailure _: + case DeleteSnapshotSuccess _: + case DeleteSnapshotFailure _: + case DeleteSnapshotsSuccess _: + case DeleteSnapshotsFailure _: + _probe.Tell(message); + return; + + default: + return; + } + } + + protected override void OnRecover(object message) + { + if (message is SnapshotOffer snapshot) + { + _probe.Tell(message); + } + } + + protected override void OnRecoveryFailure(Exception reason, object message = null) + { + _probe.Tell(new RecoveryFailure(reason, message)); + base.OnRecoveryFailure(reason, message); + } + + public class DeleteOne + { + public DeleteOne(long sequenceNr) + { + SequenceNr = sequenceNr; + } + + public long SequenceNr { get; } + } + + public class DeleteMany + { + public DeleteMany(SnapshotSelectionCriteria criteria) + { + Criteria = criteria; + } + + public SnapshotSelectionCriteria Criteria { get; } + } + + public class RecoveryFailure + { + public RecoveryFailure(Exception reason, object message) + { + Reason = reason; + Message = message; + } + + public Exception Reason { get; } + public object Message { get; } + } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs b/src/core/Akka.Persistence.TestKit.Tests/JournalInterceptorsSpecs.cs similarity index 99% rename from src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs rename to src/core/Akka.Persistence.TestKit.Tests/JournalInterceptorsSpecs.cs index 24fc63793fb..97ea1a80056 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/JournalWriteInterceptorsSpecs.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/JournalInterceptorsSpecs.cs @@ -6,7 +6,7 @@ using FluentAssertions; using Xunit; - public class JournalWriteInterceptorsSpecs + public class JournalInterceptorsSpecs { [Fact] public void noop_immediately_returns_without_exception() diff --git a/src/core/Akka.Persistence.TestKit.Tests/SnapshotStoreInterceptorsSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/SnapshotStoreInterceptorsSpec.cs new file mode 100644 index 00000000000..cefa0377272 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/SnapshotStoreInterceptorsSpec.cs @@ -0,0 +1,98 @@ +namespace Akka.Persistence.TestKit.Tests +{ + using System; + using System.Threading.Tasks; + using FluentAssertions; + using Xunit; + + public class SnapshotStoreInterceptorsSpec + { + [Fact] + public void noop_must_do_nothing() + => SnapshotStoreInterceptors.Noop.Instance + .Awaiting(x => x.InterceptAsync(null, null)) + .ShouldNotThrow(); + + [Fact] + public void failure_must_always_throw_exception() + => SnapshotStoreInterceptors.Failure.Instance + .Awaiting(x => x.InterceptAsync(null, null)) + .ShouldThrowExactly(); + + [Fact] + public async Task delay_must_call_next_interceptor_after_specified_delay() + { + var duration = TimeSpan.FromMilliseconds(100); + var probe = new InterceptorProbe(); + var delay = new SnapshotStoreInterceptors.Delay(duration, probe); + + var startedAt = DateTime.Now; + await delay.InterceptAsync(null, null); + + probe.WasCalled.Should().BeTrue(); + probe.CalledAt.Should().BeOnOrAfter(startedAt + duration); + } + + [Fact] + public async Task on_condition_must_accept_sync_lambda() + { + var probe = new InterceptorProbe(); + var onCondition = new SnapshotStoreInterceptors.OnCondition((_, __) => true, probe); + + await onCondition.InterceptAsync(null, null); + + probe.WasCalled.Should().BeTrue(); + } + + [Fact] + public async Task on_condition_must_accept_async_lambda() + { + var probe = new InterceptorProbe(); + var onCondition = new SnapshotStoreInterceptors.OnCondition((_, __) => Task.FromResult(true), probe); + + await onCondition.InterceptAsync(null, null); + + probe.WasCalled.Should().BeTrue(); + } + + [Fact] + public async Task on_condition_must_call_next_interceptor_unless_predicate_returns_false() + { + var probe = new InterceptorProbe(); + var onCondition = new SnapshotStoreInterceptors.OnCondition((_, __) => false, probe); + + await onCondition.InterceptAsync(null, null); + + probe.WasCalled.Should().BeFalse(); + } + + [Fact] + public async Task on_condition_with_negation_must_call_next_interceptor_unless_predicate_returns_true() + { + var probe = new InterceptorProbe(); + var onCondition = new SnapshotStoreInterceptors.OnCondition((_, __) => false, probe, negate: true); + + await onCondition.InterceptAsync(null, null); + + probe.WasCalled.Should().BeTrue(); + } + + public class InterceptorProbe : ISnapshotStoreInterceptor + { + public bool WasCalled { get; private set; } + public DateTime CalledAt { get; private set; } + public string PersistenceId { get; private set; } + public SnapshotSelectionCriteria Criteria { get; private set; } + + public Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria) + { + CalledAt = DateTime.Now; + WasCalled = true; + PersistenceId = persistenceId; + Criteria = criteria; + + return Task.CompletedTask; + } + } + } +} diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs index 5fcf2d3a49a..daf77340688 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -53,23 +53,7 @@ public async Task when_reject_on_write_is_set_all_writes_to_journal_will_be_reje } [Fact] - public async Task during_recovery_by_setting_fail_will_cause_recovery_failure() - { - var actor = Sys.ActorOf(); - await actor.Ask("write"); - await actor.GracefulStop(TimeSpan.FromSeconds(3)); - - WithFailingJournalRecovery(() => - { - actor = Sys.ActorOf(); - Watch(actor); - - ExpectTerminated(actor, TimeSpan.FromSeconds(3)); - }); - } - - [Fact] - public async Task new_api() + public async Task journal_must_reset_state_to_pass() { await WithJournalWrite(write => write.Fail(), () => { @@ -79,41 +63,12 @@ await WithJournalWrite(write => write.Fail(), () => actor.Tell("write", TestActor); ExpectTerminated(actor, TimeSpan.FromSeconds(3)); }); - } - } - public class PersistActor : UntypedPersistentActor - { - public override string PersistenceId => "foo"; - - protected override void OnCommand(object message) - { - var sender = Sender; - switch (message as string) - { - case "write": - Persist(message, _ => - { - sender.Tell("ack"); - }); - - break; - - default: - return; - } - } + var actor2 = Sys.ActorOf(); + Watch(actor2); - protected override void OnRecover(object message) - { - // noop - } - - protected override void OnPersistRejected(Exception cause, object @event, long sequenceNr) - { - Sender.Tell("rejected"); - - base.OnPersistRejected(cause, @event, sequenceNr); + actor2.Tell("write", TestActor); + ExpectMsg("ack", TimeSpan.FromSeconds(3)); } } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestSnapshotStoreSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestSnapshotStoreSpec.cs new file mode 100644 index 00000000000..bc146165cc6 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Tests/TestSnapshotStoreSpec.cs @@ -0,0 +1,94 @@ +namespace Akka.Persistence.TestKit.Tests +{ + using System; + using System.Threading.Tasks; + using Actor; + using Akka.TestKit; + using Xunit; + + public sealed class TestSnapshotStoreSpec : PersistenceTestKit + { + public TestSnapshotStoreSpec() + { + _probe = CreateTestProbe(); + } + + private readonly TestProbe _probe; + + [Fact] + public void send_ack_after_load_interceptor_is_set() + { + SnapshotsActorRef.Tell(new TestSnapshotStore.UseLoadInterceptor(null), TestActor); + ExpectMsg(); + } + + [Fact] + public void send_ack_after_save_interceptor_is_set() + { + SnapshotsActorRef.Tell(new TestSnapshotStore.UseSaveInterceptor(null), TestActor); + ExpectMsg(); + } + + [Fact] + public void send_ack_after_delete_interceptor_is_set() + { + SnapshotsActorRef.Tell(new TestSnapshotStore.UseDeleteInterceptor(null), TestActor); + ExpectMsg(); + } + + [Fact] + public async Task after_load_behavior_was_executed_store_is_back_to_pass_mode() + { + // create snapshot + var actor = ActorOf(() => new SnapshotActor(_probe)); + actor.Tell("save"); + _probe.ExpectMsg(); + await actor.GracefulStop(TimeSpan.FromSeconds(3)); + + await WithSnapshotLoad(load => load.Fail(), () => + { + ActorOf(() => new SnapshotActor(_probe)); + _probe.ExpectMsg(); + }); + + ActorOf(() => new SnapshotActor(_probe)); + _probe.ExpectMsg(); + } + + [Fact] + public async Task after_save_behavior_was_executed_store_is_back_to_pass_mode() + { + // create snapshot + var actor = ActorOf(() => new SnapshotActor(_probe)); + + await WithSnapshotSave(save => save.Fail(), () => + { + actor.Tell("save"); + _probe.ExpectMsg(); + }); + + actor.Tell("save"); + _probe.ExpectMsg(); + } + + [Fact] + public async Task after_delete_behavior_was_executed_store_is_back_to_pass_mode() + { + // create snapshot + var actor = ActorOf(() => new SnapshotActor(_probe)); + actor.Tell("save"); + + var success = _probe.ExpectMsg(); + var nr = success.Metadata.SequenceNr; + + await WithSnapshotDelete(del => del.Fail(), () => + { + actor.Tell(new SnapshotActor.DeleteOne(nr), TestActor); + _probe.ExpectMsg(); + }); + + actor.Tell(new SnapshotActor.DeleteOne(nr), TestActor); + _probe.ExpectMsg(); + } + } +} diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs index 06c495af206..e91f03564fc 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs @@ -6,9 +6,9 @@ public class TestSnapshotStore : MemorySnapshotStore { - private ISnapshotStoreInterceptor _saveInterceptor; - private ISnapshotStoreInterceptor _loadInterceptor; - private ISnapshotStoreInterceptor _deleteInterceptor; + private ISnapshotStoreInterceptor _saveInterceptor = SnapshotStoreInterceptors.Noop.Instance; + private ISnapshotStoreInterceptor _loadInterceptor = SnapshotStoreInterceptors.Noop.Instance; + private ISnapshotStoreInterceptor _deleteInterceptor = SnapshotStoreInterceptors.Noop.Instance; protected override bool ReceivePluginInternal(object message) { @@ -16,14 +16,17 @@ protected override bool ReceivePluginInternal(object message) { case UseSaveInterceptor use: _saveInterceptor = use.Interceptor; + Sender.Tell(Ack.Instance); return true; case UseLoadInterceptor use: _loadInterceptor = use.Interceptor; + Sender.Tell(Ack.Instance); return true; case UseDeleteInterceptor use: _deleteInterceptor = use.Interceptor; + Sender.Tell(Ack.Instance); return true; default: diff --git a/src/core/Akka.Persistence.TestKit/test-journal.conf b/src/core/Akka.Persistence.TestKit/test-journal.conf index 5598b81eefe..dda3d635272 100644 --- a/src/core/Akka.Persistence.TestKit/test-journal.conf +++ b/src/core/Akka.Persistence.TestKit/test-journal.conf @@ -9,5 +9,15 @@ akka { plugin-dispatcher = "akka.actor.default-dispatcher" } } + + snapshot-store { + plugin = "akka.persistence.snapshot-store.test" + auto-start-snapshot-stores = ["akka.persistence.snapshot-store.test"] + + test { + class = "Akka.Persistence.TestKit.TestSnapshotStore, Akka.Persistence.TestKit" + plugin-dispatcher = "akka.actor.default-dispatcher" + } + } } } \ No newline at end of file From c97481a364529824d0bdc771b1898bdd119a75fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sat, 17 Aug 2019 00:09:00 +0300 Subject: [PATCH 11/16] separate package with Xunit2 integration --- src/Akka.sln | 21 ++++++++++++--- .../Akka.Persistence.TestKit.Tests.csproj | 1 + .../Akka.Persistence.TestKit.Xunit2.csproj | 27 +++++++++++++++++++ .../PersistenceTestKit.cs | 0 .../Akka.Persistence.TestKit.csproj | 2 +- .../PersistenceTestKitBase.cs | 2 +- .../{test-journal.conf => config.conf} | 0 7 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj rename src/core/{Akka.Persistence.TestKit => Akka.Persistence.TestKit.Xunit2}/PersistenceTestKit.cs (100%) rename src/core/Akka.Persistence.TestKit/{test-journal.conf => config.conf} (100%) diff --git a/src/Akka.sln b/src/Akka.sln index 94b23d5542f..ecce82ad78e 100644 --- a/src/Akka.sln +++ b/src/Akka.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2010 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29201.188 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmark", "Benchmark", "{73108242-625A-4D7B-AA09-63375DBAE464}" EndProject @@ -199,7 +199,9 @@ Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Akka.Persistence.FSharp", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.TestKit", "core\Akka.Persistence.TestKit\Akka.Persistence.TestKit.csproj", "{212A2D35-E8D1-46A7-A1D1-418CF9509D77}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akka.Persistence.TestKit.Tests", "core\Akka.Persistence.TestKit.Tests\Akka.Persistence.TestKit.Tests.csproj", "{22F6EA86-0079-41A0-9BD3-82D2D6C34638}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.TestKit.Tests", "core\Akka.Persistence.TestKit.Tests\Akka.Persistence.TestKit.Tests.csproj", "{22F6EA86-0079-41A0-9BD3-82D2D6C34638}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Akka.Persistence.TestKit.Xunit2", "core\Akka.Persistence.TestKit.Xunit2\Akka.Persistence.TestKit.Xunit2.csproj", "{6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -872,6 +874,18 @@ Global {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x64.Build.0 = Release|Any CPU {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x86.ActiveCfg = Release|Any CPU {22F6EA86-0079-41A0-9BD3-82D2D6C34638}.Release|x86.Build.0 = Release|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Debug|x64.ActiveCfg = Debug|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Debug|x64.Build.0 = Debug|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Debug|x86.ActiveCfg = Debug|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Debug|x86.Build.0 = Debug|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Release|Any CPU.Build.0 = Release|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Release|x64.ActiveCfg = Release|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Release|x64.Build.0 = Release|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Release|x86.ActiveCfg = Release|Any CPU + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -959,6 +973,7 @@ Global {539C3EB6-FCC8-41FA-9373-364605877EE1} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} {212A2D35-E8D1-46A7-A1D1-418CF9509D77} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} {22F6EA86-0079-41A0-9BD3-82D2D6C34638} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} + {6F8FECD6-6E39-473E-9B9A-9EE22CBF479F} = {01167D3C-49C4-4CDE-9787-C176D139ACDD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {03AD8E21-7507-4E68-A4E9-F4A7E7273164} diff --git a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj index c2396b0160f..1699eb1e18a 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj +++ b/src/core/Akka.Persistence.TestKit.Tests/Akka.Persistence.TestKit.Tests.csproj @@ -18,6 +18,7 @@ + diff --git a/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj b/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj new file mode 100644 index 00000000000..4258d2c60da --- /dev/null +++ b/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj @@ -0,0 +1,27 @@ + + + + + Akka.Persistence.TestKit.Xunit2 + TestKit for writing tests for Akka.NET Persistance module. + $(NetFrameworkLibVersion);$(NetStandardLibVersion) + $(AkkaPackageTags);testkit;persistance + true + 1.6.1 + + + + + + + + + + $(DefineConstants);RELEASE + + + + + true + + \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs similarity index 100% rename from src/core/Akka.Persistence.TestKit/PersistenceTestKit.cs rename to src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj index f035ac65991..739005305ac 100644 --- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj @@ -11,7 +11,7 @@ - + diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs index 0cd5700ff6d..60311646828 100644 --- a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs @@ -136,6 +136,6 @@ public Task WithSnapshotDelete(Func behaviorS }); static Config GetConfig() - => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.test-journal.conf"); + => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.config.conf"); } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/test-journal.conf b/src/core/Akka.Persistence.TestKit/config.conf similarity index 100% rename from src/core/Akka.Persistence.TestKit/test-journal.conf rename to src/core/Akka.Persistence.TestKit/config.conf From a85d950db5d8bf97b084bf1638c17d36655ca301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sun, 18 Aug 2019 00:30:02 +0300 Subject: [PATCH 12/16] XML doc --- .../Akka.Persistence.TestKit.Xunit2.csproj | 4 +- .../PersistenceTestKit.cs | 10 ++ .../Journal/JournalRecoveryBehavior.cs | 134 ++++++++++++++- .../PersistenceTestKitBase.cs | 159 +++++++++++++++++- 4 files changed, 298 insertions(+), 9 deletions(-) diff --git a/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj b/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj index 4258d2c60da..0862b82e424 100644 --- a/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj +++ b/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj @@ -3,9 +3,9 @@ Akka.Persistence.TestKit.Xunit2 - TestKit for writing tests for Akka.NET Persistance module. + TestKit for writing tests for Akka.NET Persistance module using xUnit $(NetFrameworkLibVersion);$(NetStandardLibVersion) - $(AkkaPackageTags);testkit;persistance + $(AkkaPackageTags);testkit;persistance;xunit true 1.6.1 diff --git a/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs index ce467aac644..8961a96ad14 100644 --- a/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs +++ b/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs @@ -9,8 +9,18 @@ namespace Akka.Persistence.TestKit { using Akka.TestKit.Xunit2; + /// + /// This class represents an Akka.NET Persistence TestKit that uses xUnit + /// as its testing framework. + /// public abstract class PersistenceTestKit : PersistenceTestKitBase { + /// + /// Create a new instance of the class. + /// A new system with the specified configuration will be created. + /// + /// Optional: The name of the actor system + /// Optional: The name of the TestActor. protected PersistenceTestKit(string actorSystemName = null, string testActorName = null) : base(new XunitAssertions(), actorSystemName, testActorName) { diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs index d600289e494..c982ae52db8 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs @@ -10,19 +10,40 @@ namespace Akka.Persistence.TestKit using System; using System.Threading.Tasks; - public class JournalRecoveryBehavior + /// + /// Collection of standard Journal interceptors who will alter Recovery operation og . + /// + public sealed class JournalRecoveryBehavior { internal JournalRecoveryBehavior(IJournalBehaviorSetter setter) { this.Setter = setter; } - protected readonly IJournalBehaviorSetter Setter; + private IJournalBehaviorSetter Setter { get; } + /// + /// + /// + /// + /// public Task SetInterceptorAsync(IJournalInterceptor interceptor) => Setter.SetInterceptorAsync(interceptor); + /// + /// Use interceptor who will do nothing. + /// + /// + /// By using this interceptor recovery operation will work like + /// standard . + /// + /// public Task Pass() => SetInterceptorAsync(JournalInterceptors.Noop.Instance); + /// + /// Delay recovery by . + /// + /// Time by which recovery operation will be delayed. + /// public Task PassWithDelay(TimeSpan delay) { if (delay <= TimeSpan.Zero) @@ -33,10 +54,40 @@ public Task PassWithDelay(TimeSpan delay) return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Noop.Instance)); } + /// + /// Always fail on recovery. + /// + /// public Task Fail() => SetInterceptorAsync(JournalInterceptors.Failure.Instance); + /// + /// Fail on recovery during recovering message of type . + /// + /// + /// + /// If is interface, recovery will fail when message implements that interface. + /// + /// + /// If is class, recovery will fail when message can be assigned to . + /// + /// + /// + /// public Task FailOnType() => FailOnType(typeof(TMessage)); + /// + /// Fail on recovery during recovering message of type . + /// + /// + /// + /// If is interface, recovery will fail when message implements that interface. + /// + /// + /// If is class, recovery will fail when message can be assigned to . + /// + /// + /// + /// public Task FailOnType(Type messageType) { if (messageType is null) @@ -47,6 +98,11 @@ public Task FailOnType(Type messageType) return SetInterceptorAsync(new JournalInterceptors.OnType(messageType, JournalInterceptors.Failure.Instance)); } + /// + /// Fail on recovery if predicate will return true. + /// + /// + /// public Task FailIf(Func predicate) { if (predicate is null) @@ -57,6 +113,11 @@ public Task FailIf(Func predicate) return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); } + /// + /// Fail on recovery if async predicate will return true. + /// + /// + /// public Task FailIf(Func> predicate) { if (predicate is null) @@ -67,6 +128,11 @@ public Task FailIf(Func> predicate) return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); } + /// + /// Fail on recovery unless predicate will return true. + /// + /// + /// public Task FailUnless(Func predicate) { if (predicate is null) @@ -77,6 +143,11 @@ public Task FailUnless(Func predicate) return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); } + /// + /// Fail on recovery unless async predicate will return true. + /// + /// + /// public Task FailUnless(Func> predicate) { if (predicate is null) @@ -87,6 +158,11 @@ public Task FailUnless(Func> predicate) return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); } + /// + /// Fail on recovery after specified delay. + /// + /// + /// public Task FailWithDelay(TimeSpan delay) { if (delay <= TimeSpan.Zero) @@ -97,6 +173,13 @@ public Task FailWithDelay(TimeSpan delay) return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance)); } + /// + /// Fail on recovery with specified delay if async predicate + /// will return true. + /// + /// + /// + /// public Task FailIfWithDelay(TimeSpan delay, Func> predicate) { if (delay <= TimeSpan.Zero) @@ -115,6 +198,13 @@ public Task FailIfWithDelay(TimeSpan delay, Func + /// Fail on recovery with specified delay if predicate + /// will return true. + /// + /// + /// + /// public Task FailIfWithDelay(TimeSpan delay, Func predicate) { if (delay <= TimeSpan.Zero) @@ -133,6 +223,13 @@ public Task FailIfWithDelay(TimeSpan delay, Func + /// Fail on recovery with specified delay unless predicate + /// will return true. + /// + /// + /// + /// public Task FailUnlessWithDelay(TimeSpan delay, Func predicate) { if (delay <= TimeSpan.Zero) @@ -152,6 +249,13 @@ public Task FailUnlessWithDelay(TimeSpan delay, Func + /// Fail on recovery with specified delay unless async predicate + /// will return true. + /// + /// + /// + /// public Task FailUnlessWithDelay(TimeSpan delay, Func> predicate) { if (delay <= TimeSpan.Zero) @@ -171,8 +275,34 @@ public Task FailUnlessWithDelay(TimeSpan delay, Func + /// Fail on recovery with specified delay if recovering message of type . + /// + /// + /// + /// If is interface, recovery will fail when message implements that interface. + /// + /// + /// If is class, recovery will fail when message can be assigned to . + /// + /// + /// + /// public Task FailOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + /// + /// Fail on recovery with specified delay if recovering message of type . + /// + /// + /// + /// If is interface, recovery will fail when message implements that interface. + /// + /// + /// If is class, recovery will fail when message can be assigned to . + /// + /// + /// + /// public Task FailOnTypeWithDelay(TimeSpan delay, Type messageType) { if (delay <= TimeSpan.Zero) diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs index 60311646828..4e8cf5a9158 100644 --- a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs +++ b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs @@ -13,30 +13,64 @@ namespace Akka.Persistence.TestKit using Akka.TestKit; using Configuration; + /// + /// This class represents an Akka.NET Persistence TestKit base class which is testing framework agnostic. + /// public abstract class PersistenceTestKitBase : TestKitBase { + /// + /// Create a new instance of the class. + /// A new system with the specified configuration will be created. + /// + /// Test framework integration + /// Optional: The name of the actor system + /// Optional: The name of the TestActor. protected PersistenceTestKitBase(ITestKitAssertions assertions, string actorSystemName = null, string testActorName = null) : base(assertions, GetConfig(), actorSystemName, testActorName) { - _persistenceExtension = Persistence.Instance.Apply(Sys); + var persistenceExtension = Persistence.Instance.Apply(Sys); - JournalActorRef = _persistenceExtension.JournalFor(null); + JournalActorRef = persistenceExtension.JournalFor(null); Journal = TestJournal.FromRef(JournalActorRef); - SnapshotsActorRef = _persistenceExtension.SnapshotStoreFor(null); + SnapshotsActorRef = persistenceExtension.SnapshotStoreFor(null); Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef); } - private readonly PersistenceExtension _persistenceExtension; - + /// + /// Actor reference to persistence Journal used by current actor system. + /// public IActorRef JournalActorRef { get; } + + /// + /// Actor reference to persistence Snapshot Store used by current actor system. + /// public IActorRef SnapshotsActorRef { get; } + /// + /// + /// public ITestJournal Journal { get; } + + /// + /// + /// public ITestSnapshotStore Snapshots { get; } + /// + /// Execute delegate with Journal Behavior applied to Recovery operation. + /// + /// + /// After will be executed, Recovery behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. public async Task WithJournalRecovery(Func behaviorSelector, Func execution) { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + try { await behaviorSelector(Journal.OnRecovery); @@ -48,8 +82,20 @@ public async Task WithJournalRecovery(Func behavi } } + /// + /// Execute delegate with Journal Behavior applied to Write operation. + /// + /// + /// After will be executed, Write behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. public async Task WithJournalWrite(Func behaviorSelector, Func execution) { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + try { await behaviorSelector(Journal.OnWrite); @@ -61,22 +107,56 @@ public async Task WithJournalWrite(Func behaviorSele } } + /// + /// Execute delegate with Journal Behavior applied to Recovery operation. + /// + /// + /// After will be executed, Recovery behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. public Task WithJournalRecovery(Func behaviorSelector, Action execution) => WithJournalRecovery(behaviorSelector, () => { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + execution(); return Task.FromResult(new object()); }); + /// + /// Execute delegate with Journal Behavior applied to Write operation. + /// + /// + /// After will be executed, Write behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. public Task WithJournalWrite(Func behaviorSelector, Action execution) => WithJournalWrite(behaviorSelector, () => { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + execution(); return Task.FromResult(new object()); }); + /// + /// Execute delegate with Snapshot Store Behavior applied to Save operation. + /// + /// + /// After will be executed, Save behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. public async Task WithSnapshotSave(Func behaviorSelector, Func execution) { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + try { await behaviorSelector(Snapshots.OnSave); @@ -88,8 +168,20 @@ public async Task WithSnapshotSave(Func behavio } } + /// + /// Execute delegate with Snapshot Store Behavior applied to Load operation. + /// + /// + /// After will be executed, Load behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. public async Task WithSnapshotLoad(Func behaviorSelector, Func execution) { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + try { await behaviorSelector(Snapshots.OnLoad); @@ -101,8 +193,20 @@ public async Task WithSnapshotLoad(Func behavio } } + /// + /// Execute delegate with Snapshot Store Behavior applied to Delete operation. + /// + /// + /// After will be executed, Delete behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. public async Task WithSnapshotDelete(Func behaviorSelector, Func execution) { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + try { await behaviorSelector(Snapshots.OnDelete); @@ -114,27 +218,72 @@ public async Task WithSnapshotDelete(Func beh } } + /// + /// Execute delegate with Snapshot Store Behavior applied to Save operation. + /// + /// + /// After will be executed, Save behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. public Task WithSnapshotSave(Func behaviorSelector, Action execution) => WithSnapshotSave(behaviorSelector, () => { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + execution(); return Task.FromResult(true); }); + /// + /// Execute delegate with Snapshot Store Behavior applied to Load operation. + /// + /// + /// After will be executed, Load behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + /// Delegate which will select Snapshot Store behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. public Task WithSnapshotLoad(Func behaviorSelector, Action execution) => WithSnapshotLoad(behaviorSelector, () => { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + execution(); return Task.FromResult(true); }); + /// + /// Execute delegate with Snapshot Store Behavior applied to Delete operation. + /// + /// + /// After will be executed, Delete behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + /// Delegate which will select Snapshot Store behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. public Task WithSnapshotDelete(Func behaviorSelector, Action execution) => WithSnapshotDelete(behaviorSelector, () => { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + execution(); return Task.FromResult(true); }); + /// + /// Loads from embedded resources actor system persistence configuration with and + /// configured as default persistence plugins. + /// + /// Actor system configuration object. + /// static Config GetConfig() => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.config.conf"); } From a14b2c94409fe95717b9e9f6da7dbfdbb49744e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Sun, 18 Aug 2019 16:41:39 +0300 Subject: [PATCH 13/16] Journal Write and Recovery behavior documentation --- .../Actors/PersistActor.cs | 20 +- .../TestJournalSpec.cs | 33 +- .../Journal/JournalRecoveryBehavior.cs | 377 +++++++++++------- .../Journal/JournalWriteBehavior.cs | 355 +++++++++++++---- 4 files changed, 559 insertions(+), 226 deletions(-) diff --git a/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs b/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs index c5e6b31848d..5aedebb13bc 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/Actors/PersistActor.cs @@ -12,17 +12,23 @@ namespace Akka.Persistence.TestKit.Tests public class PersistActor : UntypedPersistentActor { + public PersistActor(IActorRef probe) + { + _probe = probe; + } + + private readonly IActorRef _probe; + public override string PersistenceId => "foo"; protected override void OnCommand(object message) { - var sender = Sender; switch (message as string) { case "write": Persist(message, _ => { - sender.Tell("ack"); + _probe.Tell("ack"); }); break; @@ -34,12 +40,18 @@ protected override void OnCommand(object message) protected override void OnRecover(object message) { - // noop + } + + protected override void OnPersistFailure(Exception cause, object @event, long sequenceNr) + { + _probe.Tell("failure"); + + base.OnPersistFailure(cause, @event, sequenceNr); } protected override void OnPersistRejected(Exception cause, object @event, long sequenceNr) { - Sender.Tell("rejected"); + _probe.Tell("rejected"); base.OnPersistRejected(cause, @event, sequenceNr); } diff --git a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs index daf77340688..4cf30b15692 100644 --- a/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs +++ b/src/core/Akka.Persistence.TestKit.Tests/TestJournalSpec.cs @@ -4,10 +4,18 @@ namespace Akka.Persistence.TestKit.Tests using System.Threading.Tasks; using Actor; using Akka.Persistence.TestKit; + using Akka.TestKit; using Xunit; public class TestJournalSpec : PersistenceTestKit { + public TestJournalSpec() + { + _probe = CreateTestProbe(); + } + + private readonly TestProbe _probe; + [Fact] public void must_return_ack_after_new_write_interceptor_is_set() { @@ -19,37 +27,37 @@ public void must_return_ack_after_new_write_interceptor_is_set() [Fact] public async Task works_as_memory_journal_by_default() { - var actor = Sys.ActorOf(); + var actor = ActorOf(() => new PersistActor(_probe)); - // should pass await Journal.OnWrite.Pass(); actor.Tell("write", TestActor); - ExpectMsg("ack", TimeSpan.FromSeconds(3)); + _probe.ExpectMsg("ack"); } [Fact] public async Task when_fail_on_write_is_set_all_writes_to_journal_will_fail() { - var actor = Sys.ActorOf(); + var actor = ActorOf(() => new PersistActor(_probe)); Watch(actor); await Journal.OnWrite.Fail(); actor.Tell("write", TestActor); - - ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + + _probe.ExpectMsg("failure"); + ExpectTerminated(actor); } [Fact] public async Task when_reject_on_write_is_set_all_writes_to_journal_will_be_rejected() { - var actor = Sys.ActorOf(); + var actor = ActorOf(() => new PersistActor(_probe)); Watch(actor); await Journal.OnWrite.Reject(); actor.Tell("write", TestActor); - ExpectMsg("rejected", TimeSpan.FromSeconds(3)); + _probe.ExpectMsg("rejected"); } [Fact] @@ -57,18 +65,19 @@ public async Task journal_must_reset_state_to_pass() { await WithJournalWrite(write => write.Fail(), () => { - var actor = Sys.ActorOf(); + var actor = ActorOf(() => new PersistActor(_probe)); Watch(actor); actor.Tell("write", TestActor); - ExpectTerminated(actor, TimeSpan.FromSeconds(3)); + _probe.ExpectMsg("failure"); + ExpectTerminated(actor); }); - var actor2 = Sys.ActorOf(); + var actor2 = ActorOf(() => new PersistActor(_probe)); Watch(actor2); actor2.Tell("write", TestActor); - ExpectMsg("ack", TimeSpan.FromSeconds(3)); + _probe.ExpectMsg("ack"); } } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs index c982ae52db8..701bdad5d3b 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehavior.cs @@ -11,9 +11,9 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; /// - /// Collection of standard Journal interceptors who will alter Recovery operation og . + /// Built-in Journal interceptors who will alter messages Recovery and/or Write of . /// - public sealed class JournalRecoveryBehavior + public class JournalRecoveryBehavior { internal JournalRecoveryBehavior(IJournalBehaviorSetter setter) { @@ -23,174 +23,235 @@ internal JournalRecoveryBehavior(IJournalBehaviorSetter setter) private IJournalBehaviorSetter Setter { get; } /// - /// + /// Use custom, user defined interceptor. /// - /// - /// - public Task SetInterceptorAsync(IJournalInterceptor interceptor) => Setter.SetInterceptorAsync(interceptor); + /// User defined interceptor which implements interface. + /// When is null. + public Task SetInterceptorAsync(IJournalInterceptor interceptor) + { + if (interceptor == null) throw new ArgumentNullException(nameof(interceptor)); + + return Setter.SetInterceptorAsync(interceptor); + } /// - /// Use interceptor who will do nothing. + /// Pass all messages to journal without interfering. /// /// - /// By using this interceptor recovery operation will work like - /// standard . + /// By using this interceptor all journal operations will work like + /// in standard . /// - /// public Task Pass() => SetInterceptorAsync(JournalInterceptors.Noop.Instance); /// - /// Delay recovery by . + /// Delay passing all messages to journal by . /// + /// + /// Each message will be delayed individually. + /// /// Time by which recovery operation will be delayed. - /// + /// When is less or equal to . public Task PassWithDelay(TimeSpan delay) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Noop.Instance)); } /// - /// Always fail on recovery. + /// Always fail all messages. /// - /// + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// public Task Fail() => SetInterceptorAsync(JournalInterceptors.Failure.Instance); /// - /// Fail on recovery during recovering message of type . + /// Fail message during processing message of type . /// /// - /// - /// If is interface, recovery will fail when message implements that interface. - /// - /// - /// If is class, recovery will fail when message can be assigned to . - /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// + /// + /// If is interface, journal will fail when message implements that interface. + /// If is class, journal will fail when message can be assigned to . + /// + /// /// /// - /// public Task FailOnType() => FailOnType(typeof(TMessage)); /// - /// Fail on recovery during recovering message of type . + /// Fail message during processing message of type . /// /// - /// - /// If is interface, recovery will fail when message implements that interface. - /// - /// - /// If is class, recovery will fail when message can be assigned to . - /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// + /// + /// If is interface, journal will fail when message implements that interface. + /// If is class, journal will fail when message can be assigned to . + /// + /// /// /// - /// + /// When is null. public Task FailOnType(Type messageType) { - if (messageType is null) - { - throw new ArgumentNullException(nameof(messageType)); - } + if (messageType == null) throw new ArgumentNullException(nameof(messageType)); return SetInterceptorAsync(new JournalInterceptors.OnType(messageType, JournalInterceptors.Failure.Instance)); } /// - /// Fail on recovery if predicate will return true. + /// Fail message if predicate will return true. /// + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// - /// + /// When is null. public Task FailIf(Func predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); } /// - /// Fail on recovery if async predicate will return true. + /// Fail message if async predicate will return true. /// + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// - /// + /// When is null. public Task FailIf(Func> predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance)); } /// - /// Fail on recovery unless predicate will return true. + /// Fail message unless predicate will return true. /// + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// - /// + /// When is null. public Task FailUnless(Func predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); } /// - /// Fail on recovery unless async predicate will return true. + /// Fail message unless async predicate will return true. /// + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// - /// + /// When is null. public Task FailUnless(Func> predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Failure.Instance, negate: true)); } /// - /// Fail on recovery after specified delay. + /// Fail message after specified delay. /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// - /// + /// When is less or equal to . public Task FailWithDelay(TimeSpan delay) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Failure.Instance)); } /// - /// Fail on recovery with specified delay if async predicate - /// will return true. + /// Fail message after specified delay if async predicate + /// will return true. /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// /// - /// + /// When is less or equal to . + /// When is null. public Task FailIfWithDelay(TimeSpan delay, Func> predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -199,23 +260,29 @@ public Task FailIfWithDelay(TimeSpan delay, Func - /// Fail on recovery with specified delay if predicate - /// will return true. + /// Fail message after specified delay if predicate + /// will return true. /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// /// - /// + /// When is less or equal to . + /// When is null. public Task FailIfWithDelay(TimeSpan delay, Func predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -224,23 +291,29 @@ public Task FailIfWithDelay(TimeSpan delay, Func - /// Fail on recovery with specified delay unless predicate - /// will return true. + /// Fail message after specified delay unless predicate + /// will return true. /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// /// - /// + /// When is less or equal to . + /// When is null. public Task FailUnlessWithDelay(TimeSpan delay, Func predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -250,23 +323,29 @@ public Task FailUnlessWithDelay(TimeSpan delay, Func - /// Fail on recovery with specified delay unless async predicate - /// will return true. + /// Fail message after specified delay unless async predicate + /// will return true. /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// /// /// - /// + /// When is less or equal to . + /// When is null. public Task FailUnlessWithDelay(TimeSpan delay, Func> predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -276,44 +355,60 @@ public Task FailUnlessWithDelay(TimeSpan delay, Func - /// Fail on recovery with specified delay if recovering message of type . + /// Fail message after specified delay if recovering message of type . /// /// - /// - /// If is interface, recovery will fail when message implements that interface. - /// - /// - /// If is class, recovery will fail when message can be assigned to . - /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// + /// + /// If is interface, journal will fail when message implements that interface. + /// If is class, journal will fail when message can be assigned to . + /// + /// + /// /// /// - /// + /// When is less or equal to . public Task FailOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); /// - /// Fail on recovery with specified delay if recovering message of type . + /// Fail message after specified delay if recovering message of type . /// /// - /// - /// If is interface, recovery will fail when message implements that interface. - /// - /// - /// If is class, recovery will fail when message can be assigned to . - /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will crash and will be called on persistent actor. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying journal. + /// + /// + /// + /// If is interface, journal will fail when message implements that interface. + /// If is class, journal will fail when message can be assigned to . + /// + /// + /// /// /// - /// + /// When is less or equal to . + /// When is null. public Task FailOnTypeWithDelay(TimeSpan delay, Type messageType) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (messageType is null) - { - throw new ArgumentNullException(nameof(messageType)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (messageType == null) throw new ArgumentNullException(nameof(messageType)); return SetInterceptorAsync(new JournalInterceptors.OnType( messageType, diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehavior.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehavior.cs index d25a16c399a..53e367f7195 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehavior.cs @@ -10,85 +10,216 @@ namespace Akka.Persistence.TestKit using System; using System.Threading.Tasks; + /// + /// Built-in Journal interceptors who will alter message Write of . + /// public class JournalWriteBehavior : JournalRecoveryBehavior { internal JournalWriteBehavior(IJournalBehaviorSetter setter) : base(setter) { } + /// + /// Always reject all messages. + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// public Task Reject() => SetInterceptorAsync(JournalInterceptors.Rejection.Instance); + /// + /// Reject message during processing message of type . + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// If is interface, journal will reject when message implements that interface. + /// If is class, journal will reject when message can be assigned to . + /// + /// + /// + /// public Task RejectOnType() => FailOnType(typeof(TMessage)); + /// + /// Reject message during processing message of type . + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// If is interface, journal will reject when message implements that interface. + /// If is class, journal will reject when message can be assigned to . + /// + /// + /// + /// + /// When is null. public Task RejectOnType(Type messageType) { - if (messageType is null) - { - throw new ArgumentNullException(nameof(messageType)); - } + if (messageType == null) throw new ArgumentNullException(nameof(messageType)); return SetInterceptorAsync(new JournalInterceptors.OnType(messageType, JournalInterceptors.Rejection.Instance)); } + /// + /// Reject message if predicate will return true. + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// When is null. public Task RejectIf(Func predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); } + /// + /// Reject message if async predicate will return true. + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// When is null. public Task RejectIf(Func> predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance)); } + /// + /// Reject message unless predicate will return true. + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// When is null. public Task RejectUnless(Func predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); } + /// + /// Reject message unless async predicate will return true. + /// + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// When is null. public Task RejectUnless(Func> predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition(predicate, JournalInterceptors.Rejection.Instance, negate: true)); } + /// + /// Reject message after specified delay. + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// When is less or equal to . public Task RejectWithDelay(TimeSpan delay) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); return SetInterceptorAsync(new JournalInterceptors.Delay(delay, JournalInterceptors.Rejection.Instance)); } + /// + /// Reject message after specified delay if async predicate + /// will return true. + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task RejectIfWithDelay(TimeSpan delay, Func> predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -96,17 +227,31 @@ public Task RejectIfWithDelay(TimeSpan delay, Func + /// Reject message after specified delay if predicate + /// will return true. + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task RejectIfWithDelay(TimeSpan delay, Func predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -114,17 +259,31 @@ public Task RejectIfWithDelay(TimeSpan delay, Func + /// Reject message after specified delay unless predicate + /// will return true. + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task RejectUnlessWithDelay(TimeSpan delay, Func predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -133,17 +292,31 @@ public Task RejectUnlessWithDelay(TimeSpan delay, Func + /// Reject message after specified delay unless async predicate + /// will return true. + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task RejectUnlessWithDelay(TimeSpan delay, Func> predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new JournalInterceptors.OnCondition( predicate, @@ -152,19 +325,63 @@ public Task RejectUnlessWithDelay(TimeSpan delay, Func + /// Reject message after specified delay if recovering message of type . + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// If is interface, journal will reject when message implements that interface. + /// If is class, journal will reject when message can be assigned to . + /// + /// + /// + /// + /// + /// When is less or equal to . public Task RejectOnTypeWithDelay(TimeSpan delay) => FailOnTypeWithDelay(delay, typeof(TMessage)); + /// + /// Reject message after specified delay if recovering message of type . + /// + /// + /// + /// Each message will be delayed individually. + /// + /// + /// Journal will **NOT** crash, but will be called + /// on each rejected message. + /// + /// + /// Use this Journal behavior when it is needed to verify how well a persistent actor will handle serialization, + /// type conversion and other local problems withing journal. + /// + /// + /// + /// If is interface, journal will reject when message implements that interface. + /// If is class, journal will reject when message can be assigned to . + /// + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task RejectOnTypeWithDelay(TimeSpan delay, Type messageType) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (messageType is null) - { - throw new ArgumentNullException(nameof(messageType)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (messageType == null) throw new ArgumentNullException(nameof(messageType)); return SetInterceptorAsync(new JournalInterceptors.OnType( messageType, From c05ec2746970fa30f91195add2a054dac380a47c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Fri, 23 Aug 2019 00:03:39 +0300 Subject: [PATCH 14/16] removed unneeded statements from csproj files --- .../Akka.Persistence.TestKit.Xunit2.csproj | 1 - .../Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj | 1 - 2 files changed, 2 deletions(-) diff --git a/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj b/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj index 0862b82e424..c4567057483 100644 --- a/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj +++ b/src/core/Akka.Persistence.TestKit.Xunit2/Akka.Persistence.TestKit.Xunit2.csproj @@ -7,7 +7,6 @@ $(NetFrameworkLibVersion);$(NetStandardLibVersion) $(AkkaPackageTags);testkit;persistance;xunit true - 1.6.1 diff --git a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj index 739005305ac..18a087e7133 100644 --- a/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj +++ b/src/core/Akka.Persistence.TestKit/Akka.Persistence.TestKit.csproj @@ -7,7 +7,6 @@ $(NetFrameworkLibVersion);$(NetStandardLibVersion) $(AkkaPackageTags);testkit;persistance true - 1.6.1 From c4cebe05f5903a017371c8cf55adb64213ebd98b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Fri, 23 Aug 2019 00:04:23 +0300 Subject: [PATCH 15/16] PersistenceTestKitBase is removed to correctly add ITestOutputHelper from xUnit --- .../PersistenceTestKit.cs | 263 +++++++++++++++- .../PersistenceTestKitBase.cs | 290 ------------------ 2 files changed, 259 insertions(+), 294 deletions(-) delete mode 100644 src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs diff --git a/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs b/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs index 8961a96ad14..9d5bb92eb84 100644 --- a/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs +++ b/src/core/Akka.Persistence.TestKit.Xunit2/PersistenceTestKit.cs @@ -7,23 +7,278 @@ namespace Akka.Persistence.TestKit { + using System; + using System.Threading.Tasks; + using Actor; using Akka.TestKit.Xunit2; + using Configuration; + using Xunit.Abstractions; /// /// This class represents an Akka.NET Persistence TestKit that uses xUnit /// as its testing framework. /// - public abstract class PersistenceTestKit : PersistenceTestKitBase + public abstract class PersistenceTestKit : TestKit { /// /// Create a new instance of the class. /// A new system with the specified configuration will be created. /// /// Optional: The name of the actor system - /// Optional: The name of the TestActor. - protected PersistenceTestKit(string actorSystemName = null, string testActorName = null) - : base(new XunitAssertions(), actorSystemName, testActorName) + protected PersistenceTestKit(string actorSystemName = null, ITestOutputHelper output = null) + : base(GetConfig(), actorSystemName, output) { + var persistenceExtension = Persistence.Instance.Apply(Sys); + + JournalActorRef = persistenceExtension.JournalFor(null); + Journal = TestJournal.FromRef(JournalActorRef); + + SnapshotsActorRef = persistenceExtension.SnapshotStoreFor(null); + Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef); + } + + /// + /// Actor reference to persistence Journal used by current actor system. + /// + public IActorRef JournalActorRef { get; } + + /// + /// Actor reference to persistence Snapshot Store used by current actor system. + /// + public IActorRef SnapshotsActorRef { get; } + + /// + /// + /// + public ITestJournal Journal { get; } + + /// + /// + /// + public ITestSnapshotStore Snapshots { get; } + + /// + /// Execute delegate with Journal Behavior applied to Recovery operation. + /// + /// + /// After will be executed, Recovery behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public async Task WithJournalRecovery(Func behaviorSelector, Func execution) + { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + try + { + await behaviorSelector(Journal.OnRecovery); + await execution(); + } + finally + { + await Journal.OnRecovery.Pass(); + } + } + + /// + /// Execute delegate with Journal Behavior applied to Write operation. + /// + /// + /// After will be executed, Write behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public async Task WithJournalWrite(Func behaviorSelector, Func execution) + { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + try + { + await behaviorSelector(Journal.OnWrite); + await execution(); + } + finally + { + await Journal.OnWrite.Pass(); + } + } + + /// + /// Execute delegate with Journal Behavior applied to Recovery operation. + /// + /// + /// After will be executed, Recovery behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public Task WithJournalRecovery(Func behaviorSelector, Action execution) + => WithJournalRecovery(behaviorSelector, () => + { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + execution(); + return Task.FromResult(new object()); + }); + + /// + /// Execute delegate with Journal Behavior applied to Write operation. + /// + /// + /// After will be executed, Write behavior will be reverted back to normal. + /// + /// Delegate which will select Journal behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public Task WithJournalWrite(Func behaviorSelector, Action execution) + => WithJournalWrite(behaviorSelector, () => + { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + execution(); + return Task.FromResult(new object()); + }); + + /// + /// Execute delegate with Snapshot Store Behavior applied to Save operation. + /// + /// + /// After will be executed, Save behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public async Task WithSnapshotSave(Func behaviorSelector, Func execution) + { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + try + { + await behaviorSelector(Snapshots.OnSave); + await execution(); + } + finally + { + await Snapshots.OnSave.Pass(); + } } + + /// + /// Execute delegate with Snapshot Store Behavior applied to Load operation. + /// + /// + /// After will be executed, Load behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public async Task WithSnapshotLoad(Func behaviorSelector, Func execution) + { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + try + { + await behaviorSelector(Snapshots.OnLoad); + await execution(); + } + finally + { + await Snapshots.OnLoad.Pass(); + } + } + + /// + /// Execute delegate with Snapshot Store Behavior applied to Delete operation. + /// + /// + /// After will be executed, Delete behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public async Task WithSnapshotDelete(Func behaviorSelector, Func execution) + { + if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + try + { + await behaviorSelector(Snapshots.OnDelete); + await execution(); + } + finally + { + await Snapshots.OnDelete.Pass(); + } + } + + /// + /// Execute delegate with Snapshot Store Behavior applied to Save operation. + /// + /// + /// After will be executed, Save behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public Task WithSnapshotSave(Func behaviorSelector, Action execution) + => WithSnapshotSave(behaviorSelector, () => + { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + execution(); + return Task.FromResult(true); + }); + + /// + /// Execute delegate with Snapshot Store Behavior applied to Load operation. + /// + /// + /// After will be executed, Load behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public Task WithSnapshotLoad(Func behaviorSelector, Action execution) + => WithSnapshotLoad(behaviorSelector, () => + { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + execution(); + return Task.FromResult(true); + }); + + /// + /// Execute delegate with Snapshot Store Behavior applied to Delete operation. + /// + /// + /// After will be executed, Delete behavior will be reverted back to normal. + /// + /// Delegate which will select Snapshot Store behavior. + /// Async delegate which will be executed with applied Journal behavior. + /// which must be awaited. + public Task WithSnapshotDelete(Func behaviorSelector, Action execution) + => WithSnapshotDelete(behaviorSelector, () => + { + if (execution == null) throw new ArgumentNullException(nameof(execution)); + + execution(); + return Task.FromResult(true); + }); + + /// + /// Loads from embedded resources actor system persistence configuration with and + /// configured as default persistence plugins. + /// + /// Actor system configuration object. + /// + static Config GetConfig() + => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.config.conf"); } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs b/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs deleted file mode 100644 index 4e8cf5a9158..00000000000 --- a/src/core/Akka.Persistence.TestKit/PersistenceTestKitBase.cs +++ /dev/null @@ -1,290 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (C) 2009-2019 Lightbend Inc. -// Copyright (C) 2013-2019 .NET Foundation -// -//----------------------------------------------------------------------- - -namespace Akka.Persistence.TestKit -{ - using System; - using System.Threading.Tasks; - using Actor; - using Akka.TestKit; - using Configuration; - - /// - /// This class represents an Akka.NET Persistence TestKit base class which is testing framework agnostic. - /// - public abstract class PersistenceTestKitBase : TestKitBase - { - /// - /// Create a new instance of the class. - /// A new system with the specified configuration will be created. - /// - /// Test framework integration - /// Optional: The name of the actor system - /// Optional: The name of the TestActor. - protected PersistenceTestKitBase(ITestKitAssertions assertions, string actorSystemName = null, string testActorName = null) - : base(assertions, GetConfig(), actorSystemName, testActorName) - { - var persistenceExtension = Persistence.Instance.Apply(Sys); - - JournalActorRef = persistenceExtension.JournalFor(null); - Journal = TestJournal.FromRef(JournalActorRef); - - SnapshotsActorRef = persistenceExtension.SnapshotStoreFor(null); - Snapshots = TestSnapshotStore.FromRef(SnapshotsActorRef); - } - - /// - /// Actor reference to persistence Journal used by current actor system. - /// - public IActorRef JournalActorRef { get; } - - /// - /// Actor reference to persistence Snapshot Store used by current actor system. - /// - public IActorRef SnapshotsActorRef { get; } - - /// - /// - /// - public ITestJournal Journal { get; } - - /// - /// - /// - public ITestSnapshotStore Snapshots { get; } - - /// - /// Execute delegate with Journal Behavior applied to Recovery operation. - /// - /// - /// After will be executed, Recovery behavior will be reverted back to normal. - /// - /// Delegate which will select Journal behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public async Task WithJournalRecovery(Func behaviorSelector, Func execution) - { - if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - try - { - await behaviorSelector(Journal.OnRecovery); - await execution(); - } - finally - { - await Journal.OnRecovery.Pass(); - } - } - - /// - /// Execute delegate with Journal Behavior applied to Write operation. - /// - /// - /// After will be executed, Write behavior will be reverted back to normal. - /// - /// Delegate which will select Journal behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public async Task WithJournalWrite(Func behaviorSelector, Func execution) - { - if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - try - { - await behaviorSelector(Journal.OnWrite); - await execution(); - } - finally - { - await Journal.OnWrite.Pass(); - } - } - - /// - /// Execute delegate with Journal Behavior applied to Recovery operation. - /// - /// - /// After will be executed, Recovery behavior will be reverted back to normal. - /// - /// Delegate which will select Journal behavior. - /// Delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public Task WithJournalRecovery(Func behaviorSelector, Action execution) - => WithJournalRecovery(behaviorSelector, () => - { - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - execution(); - return Task.FromResult(new object()); - }); - - /// - /// Execute delegate with Journal Behavior applied to Write operation. - /// - /// - /// After will be executed, Write behavior will be reverted back to normal. - /// - /// Delegate which will select Journal behavior. - /// Delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public Task WithJournalWrite(Func behaviorSelector, Action execution) - => WithJournalWrite(behaviorSelector, () => - { - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - execution(); - return Task.FromResult(new object()); - }); - - /// - /// Execute delegate with Snapshot Store Behavior applied to Save operation. - /// - /// - /// After will be executed, Save behavior will be reverted back to normal. - /// - /// Delegate which will select Snapshot Store behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public async Task WithSnapshotSave(Func behaviorSelector, Func execution) - { - if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - try - { - await behaviorSelector(Snapshots.OnSave); - await execution(); - } - finally - { - await Snapshots.OnSave.Pass(); - } - } - - /// - /// Execute delegate with Snapshot Store Behavior applied to Load operation. - /// - /// - /// After will be executed, Load behavior will be reverted back to normal. - /// - /// Delegate which will select Snapshot Store behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public async Task WithSnapshotLoad(Func behaviorSelector, Func execution) - { - if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - try - { - await behaviorSelector(Snapshots.OnLoad); - await execution(); - } - finally - { - await Snapshots.OnLoad.Pass(); - } - } - - /// - /// Execute delegate with Snapshot Store Behavior applied to Delete operation. - /// - /// - /// After will be executed, Delete behavior will be reverted back to normal. - /// - /// Delegate which will select Snapshot Store behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public async Task WithSnapshotDelete(Func behaviorSelector, Func execution) - { - if (behaviorSelector == null) throw new ArgumentNullException(nameof(behaviorSelector)); - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - try - { - await behaviorSelector(Snapshots.OnDelete); - await execution(); - } - finally - { - await Snapshots.OnDelete.Pass(); - } - } - - /// - /// Execute delegate with Snapshot Store Behavior applied to Save operation. - /// - /// - /// After will be executed, Save behavior will be reverted back to normal. - /// - /// Delegate which will select Snapshot Store behavior. - /// Delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public Task WithSnapshotSave(Func behaviorSelector, Action execution) - => WithSnapshotSave(behaviorSelector, () => - { - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - execution(); - return Task.FromResult(true); - }); - - /// - /// Execute delegate with Snapshot Store Behavior applied to Load operation. - /// - /// - /// After will be executed, Load behavior will be reverted back to normal. - /// - /// Delegate which will select Snapshot Store behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - /// Delegate which will select Snapshot Store behavior. - /// Delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public Task WithSnapshotLoad(Func behaviorSelector, Action execution) - => WithSnapshotLoad(behaviorSelector, () => - { - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - execution(); - return Task.FromResult(true); - }); - - /// - /// Execute delegate with Snapshot Store Behavior applied to Delete operation. - /// - /// - /// After will be executed, Delete behavior will be reverted back to normal. - /// - /// Delegate which will select Snapshot Store behavior. - /// Async delegate which will be executed with applied Journal behavior. - /// which must be awaited. - /// Delegate which will select Snapshot Store behavior. - /// Delegate which will be executed with applied Journal behavior. - /// which must be awaited. - public Task WithSnapshotDelete(Func behaviorSelector, Action execution) - => WithSnapshotDelete(behaviorSelector, () => - { - if (execution == null) throw new ArgumentNullException(nameof(execution)); - - execution(); - return Task.FromResult(true); - }); - - /// - /// Loads from embedded resources actor system persistence configuration with and - /// configured as default persistence plugins. - /// - /// Actor system configuration object. - /// - static Config GetConfig() - => ConfigurationFactory.FromResource("Akka.Persistence.TestKit.config.conf"); - } -} \ No newline at end of file From 114796861b47ff07013ab9de0be070d922aee110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Valdis=20Zob=C4=93la?= Date: Fri, 23 Aug 2019 11:59:14 +0300 Subject: [PATCH 16/16] added mor xml docs --- .../Journal/IJournalInterceptor.cs | 7 + .../Journal/ITestJournal.cs | 9 + .../Journal/JournalRecoveryBehaviorSetter.cs | 3 + .../Journal/JournalWriteBehaviorSetter.cs | 3 + .../Journal/TestJournal.cs | 11 + .../Journal/TestJournalFailureException.cs | 9 - .../Journal/TestJournalRejectionException.cs | 21 ++ .../ISnapshotStoreInterceptor.cs | 6 + .../SnapshotStoreDeleteBehaviorSetter.cs | 3 + .../SnapshotStoreLoadBehaviorSetter.cs | 3 + .../SnapshotStoreSaveBehavior.cs | 259 +++++++++++++----- .../SnapshotStoreSaveBehaviorSetter.cs | 3 + .../SnapshotStore/TestSnapshotStore.cs | 11 + 13 files changed, 278 insertions(+), 70 deletions(-) create mode 100644 src/core/Akka.Persistence.TestKit/Journal/TestJournalRejectionException.cs diff --git a/src/core/Akka.Persistence.TestKit/Journal/IJournalInterceptor.cs b/src/core/Akka.Persistence.TestKit/Journal/IJournalInterceptor.cs index cdf2c6d4aaf..14a5a8ea3cd 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/IJournalInterceptor.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/IJournalInterceptor.cs @@ -2,8 +2,15 @@ { using System.Threading.Tasks; + /// + /// Interface to object which will intercept written and recovered messages in . + /// public interface IJournalInterceptor { + /// + /// Method will be called for each individual message before it is written or recovered. + /// + /// Written or recovered message. Task InterceptAsync(IPersistentRepresentation message); } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/Journal/ITestJournal.cs b/src/core/Akka.Persistence.TestKit/Journal/ITestJournal.cs index 314a6159c1d..c06b37b9c35 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/ITestJournal.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/ITestJournal.cs @@ -7,10 +7,19 @@ namespace Akka.Persistence.TestKit { + /// + /// proxy object interface. Used to simplify communication with actor instance. + /// public interface ITestJournal { + /// + /// List of interceptors to alter write behavior of proxied journal. + /// JournalWriteBehavior OnWrite { get; } + /// + /// List of interceptors to alter recovery behavior of proxied journal. + /// JournalRecoveryBehavior OnRecovery { get; } } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs index d184d364334..4822b995148 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalRecoveryBehaviorSetter.cs @@ -11,6 +11,9 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; using Actor; + /// + /// Setter strategy for TestJournal which will set recovery interceptor. + /// internal class JournalRecoveryBehaviorSetter : IJournalBehaviorSetter { internal JournalRecoveryBehaviorSetter(IActorRef journal) diff --git a/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs index fdd54a19409..98b25a4cebe 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/JournalWriteBehaviorSetter.cs @@ -11,6 +11,9 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; using Actor; + /// + /// Setter strategy for which will set write interceptor. + /// internal class JournalWriteBehaviorSetter : IJournalBehaviorSetter { internal JournalWriteBehaviorSetter(IActorRef journal) diff --git a/src/core/Akka.Persistence.TestKit/Journal/TestJournal.cs b/src/core/Akka.Persistence.TestKit/Journal/TestJournal.cs index 56749fd6255..8396d429a84 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/TestJournal.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/TestJournal.cs @@ -15,6 +15,9 @@ namespace Akka.Persistence.TestKit using System.Collections.Immutable; using System.Threading.Tasks; + /// + /// In-memory persistence journal implementation which behavior could be controlled by interceptors. + /// public sealed class TestJournal : MemoryJournal { private IJournalInterceptor _writeInterceptor = JournalInterceptors.Noop.Instance; @@ -91,6 +94,14 @@ public override async Task ReplayMessagesAsync(IActorContext context, string per } } + /// + /// Create proxy object from journal actor reference which can alter behavior of journal. + /// + /// + /// Journal actor must be of type. + /// + /// Journal actor reference. + /// Proxy object to control . public static ITestJournal FromRef(IActorRef actor) { return new TestJournalWrapper(actor); diff --git a/src/core/Akka.Persistence.TestKit/Journal/TestJournalFailureException.cs b/src/core/Akka.Persistence.TestKit/Journal/TestJournalFailureException.cs index ea167d2efa3..b95005060b8 100644 --- a/src/core/Akka.Persistence.TestKit/Journal/TestJournalFailureException.cs +++ b/src/core/Akka.Persistence.TestKit/Journal/TestJournalFailureException.cs @@ -18,13 +18,4 @@ public TestJournalFailureException(string message) : base(message) { } public TestJournalFailureException(string message, Exception inner) : base(message, inner) { } protected TestJournalFailureException(SerializationInfo info, StreamingContext context) : base(info, context) { } } - - [Serializable] - public class TestJournalRejectionException : Exception - { - public TestJournalRejectionException() { } - public TestJournalRejectionException(string message) : base(message) { } - public TestJournalRejectionException(string message, Exception inner) : base(message, inner) { } - protected TestJournalRejectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } - } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/Journal/TestJournalRejectionException.cs b/src/core/Akka.Persistence.TestKit/Journal/TestJournalRejectionException.cs new file mode 100644 index 00000000000..3e1c8d01290 --- /dev/null +++ b/src/core/Akka.Persistence.TestKit/Journal/TestJournalRejectionException.cs @@ -0,0 +1,21 @@ +// ----------------------------------------------------------------------- +// +// Copyright (C) 2009-2019 Lightbend Inc. +// Copyright (C) 2013-2019 .NET Foundation +// +// ----------------------------------------------------------------------- + +namespace Akka.Persistence.TestKit +{ + using System; + using System.Runtime.Serialization; + + [Serializable] + public class TestJournalRejectionException : Exception + { + public TestJournalRejectionException() { } + public TestJournalRejectionException(string message) : base(message) { } + public TestJournalRejectionException(string message, Exception inner) : base(message, inner) { } + protected TestJournalRejectionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs index 98b7be6665d..4a2054c008c 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/ISnapshotStoreInterceptor.cs @@ -9,8 +9,14 @@ namespace Akka.Persistence.TestKit { using System.Threading.Tasks; + /// + /// Interface to object which will intercept all action in . + /// public interface ISnapshotStoreInterceptor { + /// + /// Method will be called for each load, save or delete attempt in . + /// Task InterceptAsync(string persistenceId, SnapshotSelectionCriteria criteria); } } \ No newline at end of file diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs index 89b95bb3152..9764f121a12 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreDeleteBehaviorSetter.cs @@ -11,6 +11,9 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; using Actor; + /// + /// Setter strategy for which will set delete interceptor. + /// internal class SnapshotStoreDeleteBehaviorSetter : ISnapshotStoreBehaviorSetter { internal SnapshotStoreDeleteBehaviorSetter(IActorRef snapshots) diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs index 05859d0a228..b0e34e3edf7 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreLoadBehaviorSetter.cs @@ -11,6 +11,9 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; using Actor; + /// + /// Setter strategy for which will set load interceptor. + /// internal class SnapshotStoreLoadBehaviorSetter : ISnapshotStoreBehaviorSetter { internal SnapshotStoreLoadBehaviorSetter(IActorRef snapshots) diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs index a4b467a8349..da25961a89d 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehavior.cs @@ -13,6 +13,9 @@ namespace Akka.Persistence.TestKit using InterceptorPredicate = System.Func; using AsyncInterceptorPredicate = System.Func>; + /// + /// Built-in snapshot store interceptors who will alter message Write of . + /// public class SnapshotStoreSaveBehavior { public SnapshotStoreSaveBehavior(ISnapshotStoreBehaviorSetter setter) @@ -22,83 +25,187 @@ public SnapshotStoreSaveBehavior(ISnapshotStoreBehaviorSetter setter) protected readonly ISnapshotStoreBehaviorSetter Setter; - public Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor) => Setter.SetInterceptorAsync(interceptor); + /// + /// Use custom, user defined interceptor. + /// + /// User defined interceptor which implements interface. + /// When is null. + public Task SetInterceptorAsync(ISnapshotStoreInterceptor interceptor) + { + if (interceptor == null) throw new ArgumentNullException(nameof(interceptor)); + + return Setter.SetInterceptorAsync(interceptor); + } + /// + /// Pass all messages to snapshot store without interfering. + /// + /// + /// By using this interceptor all snapshot store operations will work like + /// in standard . + /// public Task Pass() => SetInterceptorAsync(SnapshotStoreInterceptors.Noop.Instance); + /// + /// Delay passing all messages to snapshot store by . + /// + /// + /// Each message will be delayed individually. + /// + /// Time by which recovery operation will be delayed. + /// When is less or equal to . public Task PassWithDelay(TimeSpan delay) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); return SetInterceptorAsync(new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Noop.Instance)); } + /// + /// Always fail all messages. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying snapshot store. + /// + /// public Task Fail() => SetInterceptorAsync(SnapshotStoreInterceptors.Failure.Instance); + /// + /// Fail message if predicate will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying snapshot store. + /// + /// + /// + /// When is null. public Task FailIf(InterceptorPredicate predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance)); } + /// + /// Fail message if async predicate will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying snapshot store. + /// + /// + /// + /// When is null. public Task FailIf(AsyncInterceptorPredicate predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance)); } + /// + /// Fail message unless predicate will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying snapshot store. + /// + /// + /// + /// When is null. public Task FailUnless(InterceptorPredicate predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance, negate: true)); } + /// + /// Fail message unless async predicate will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// and similar issues with underlying snapshot store. + /// + /// + /// + /// When is null. public Task FailUnless(AsyncInterceptorPredicate predicate) { - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition(predicate, SnapshotStoreInterceptors.Failure.Instance, negate: true)); } + /// + /// Fail message after specified delay. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// + /// + /// + /// When is less or equal to . public Task FailWithDelay(TimeSpan delay) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); return SetInterceptorAsync(new SnapshotStoreInterceptors.Delay(delay, SnapshotStoreInterceptors.Failure.Instance)); } + /// + /// Fail message after specified delay if async predicate + /// will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task FailIfWithDelay(TimeSpan delay, AsyncInterceptorPredicate predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( predicate, @@ -106,17 +213,27 @@ public Task FailIfWithDelay(TimeSpan delay, AsyncInterceptorPredicate predicate) )); } + /// + /// Fail message after specified delay if predicate + /// will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task FailIfWithDelay(TimeSpan delay, InterceptorPredicate predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( predicate, @@ -124,17 +241,27 @@ public Task FailIfWithDelay(TimeSpan delay, InterceptorPredicate predicate) )); } + /// + /// Fail message after specified delay unless predicate + /// will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task FailUnlessWithDelay(TimeSpan delay, InterceptorPredicate predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( predicate, @@ -143,17 +270,27 @@ public Task FailUnlessWithDelay(TimeSpan delay, InterceptorPredicate predicate) )); } + /// + /// Fail message after specified delay unless async predicate + /// will return true. + /// + /// + /// + /// Snapshot store will crash and actor will receive one of , + /// or messages. + /// + /// + /// Use this snapshot store behavior when it is needed to verify how well a persistent actor will handle network problems + /// + /// + /// + /// + /// When is less or equal to . + /// When is null. public Task FailUnlessWithDelay(TimeSpan delay, AsyncInterceptorPredicate predicate) { - if (delay <= TimeSpan.Zero) - { - throw new ArgumentException("Delay must be greater than zero", nameof(delay)); - } - - if (predicate is null) - { - throw new ArgumentNullException(nameof(predicate)); - } + if (delay <= TimeSpan.Zero) throw new ArgumentException("Delay must be greater than zero", nameof(delay)); + if (predicate == null) throw new ArgumentNullException(nameof(predicate)); return SetInterceptorAsync(new SnapshotStoreInterceptors.OnCondition( predicate, diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs index dd7d2e9a6c6..b2ad4581177 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/SnapshotStoreSaveBehaviorSetter.cs @@ -11,6 +11,9 @@ namespace Akka.Persistence.TestKit using System.Threading.Tasks; using Actor; + /// + /// Setter strategy for which will set save interceptor. + /// internal class SnapshotStoreSaveBehaviorSetter : ISnapshotStoreBehaviorSetter { internal SnapshotStoreSaveBehaviorSetter(IActorRef snapshots) diff --git a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs index e91f03564fc..fe863c69bdb 100644 --- a/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs +++ b/src/core/Akka.Persistence.TestKit/SnapshotStore/TestSnapshotStore.cs @@ -4,6 +4,9 @@ using Actor; using Snapshot; + /// + /// In-memory persistence snapshot store implementation which behavior could be controlled by interceptors. + /// public class TestSnapshotStore : MemorySnapshotStore { private ISnapshotStoreInterceptor _saveInterceptor = SnapshotStoreInterceptors.Noop.Instance; @@ -62,6 +65,14 @@ static SnapshotSelectionCriteria ToSelectionCriteria(SnapshotMetadata metadata) => new SnapshotSelectionCriteria(metadata.SequenceNr, metadata.Timestamp, metadata.SequenceNr, metadata.Timestamp); + /// + /// Create proxy object from snapshot store actor reference which can alter behavior of snapshot store. + /// + /// + /// Snapshot store actor must be of type. + /// + /// Journal actor reference. + /// Proxy object to control . public static ITestSnapshotStore FromRef(IActorRef actor) { return new TestSnapshotStoreWrapper(actor);