diff --git a/tests/Prism.Plugin.Popups.DryIoc.Tests/Prism.Plugin.Popups.DryIoc.Tests.csproj b/tests/Prism.Plugin.Popups.DryIoc.Tests/Prism.Plugin.Popups.DryIoc.Tests.csproj index d363741..f8a59f6 100644 --- a/tests/Prism.Plugin.Popups.DryIoc.Tests/Prism.Plugin.Popups.DryIoc.Tests.csproj +++ b/tests/Prism.Plugin.Popups.DryIoc.Tests/Prism.Plugin.Popups.DryIoc.Tests.csproj @@ -16,7 +16,6 @@ all runtime; build; native; contentfiles; analyzers - diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/DelayedMessageBus.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/DelayedMessageBus.cs new file mode 100644 index 0000000..21a2275 --- /dev/null +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/DelayedMessageBus.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit +{ + /// + /// Used to capture messages to potentially be forwarded later. Messages are forwarded by + /// disposing of the message bus. + /// + public class DelayedMessageBus : IMessageBus + { + private readonly IMessageBus innerBus; + private readonly List messages = new List(); + + public DelayedMessageBus(IMessageBus innerBus) + { + this.innerBus = innerBus; + } + + public bool QueueMessage(IMessageSinkMessage message) + { + lock (messages) + messages.Add(message); + + // No way to ask the inner bus if they want to cancel without sending them the message, so + // we just go ahead and continue always. + return true; + } + + public void Dispose() + { + foreach (var message in messages) + innerBus.QueueMessage(message); + } + } +} \ No newline at end of file diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryAttribute.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryAttribute.cs new file mode 100644 index 0000000..955c677 --- /dev/null +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryAttribute.cs @@ -0,0 +1,27 @@ +using Xunit; +using Xunit.Sdk; + +namespace Xunit +{ + /// + /// Works just like [Fact] except that failures are retried (by default, 3 times). + /// +#if DRYIOC + [XunitTestCaseDiscoverer("Xunit.RetryFactDiscoverer", "Prism.Plugin.Popups.DryIoc.Tests")] +#else + [XunitTestCaseDiscoverer("Xunit.RetryFactDiscoverer", "Prism.Plugin.Popups.Unity.Tests")] +#endif + public class RetryAttribute : FactAttribute + { + public RetryAttribute(int maxRetries = 3) + { + MaxRetries = maxRetries; + } + + /// + /// Number of retries allowed for a failed test. If unset (or set less than 1), will + /// default to 3 attempts. + /// + public int MaxRetries { get; set; } + } +} \ No newline at end of file diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryFactDiscoverer.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryFactDiscoverer.cs new file mode 100644 index 0000000..f2b7798 --- /dev/null +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryFactDiscoverer.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit +{ + public class RetryFactDiscoverer : IXunitTestCaseDiscoverer + { + readonly IMessageSink diagnosticMessageSink; + + public RetryFactDiscoverer(IMessageSink diagnosticMessageSink) + { + this.diagnosticMessageSink = diagnosticMessageSink; + } + + public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + { + var maxRetries = factAttribute.GetNamedArgument("MaxRetries"); + if (maxRetries < 1) + maxRetries = 3; + + yield return new RetryTestCase(diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), TestMethodDisplayOptions.All, testMethod, maxRetries); + } + } +} \ No newline at end of file diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryTestCase.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryTestCase.cs new file mode 100644 index 0000000..787f7cd --- /dev/null +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Helpers/RetryTestCase.cs @@ -0,0 +1,79 @@ +using System; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; +using Xunit.Abstractions; +using Xunit.Sdk; + +namespace Xunit +{ + [Serializable] + public class RetryTestCase : XunitTestCase + { + private int maxRetries; + + [EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Called by the de-serializer", true)] + public RetryTestCase() { } + + //[Obsolete] + //public RetryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay testMethodDisplay, ITestMethod testMethod, int maxRetries) + // : base(diagnosticMessageSink, testMethodDisplay, testMethod, testMethodArguments: null) + //{ + // this.maxRetries = maxRetries; + //} + + public RetryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, int maxRetries, object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) + { + this.maxRetries = maxRetries; + } + + // This method is called by the xUnit test framework classes to run the test case. We will do the + // loop here, forwarding on to the implementation in XunitTestCase to do the heavy lifting. We will + // continue to re-run the test until the aggregator has an error (meaning that some internal error + // condition happened), or the test runs without failure, or we've hit the maximum number of tries. + public override async Task RunAsync(IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + object[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) + { + var runCount = 0; + + while (true) + { + // This is really the only tricky bit: we need to capture and delay messages (since those will + // contain run status) until we know we've decided to accept the final result; + var delayedMessageBus = new DelayedMessageBus(messageBus); + + var summary = await base.RunAsync(diagnosticMessageSink, delayedMessageBus, constructorArguments, aggregator, cancellationTokenSource); + if (aggregator.HasExceptions || summary.Failed == 0 || ++runCount >= maxRetries) + { + delayedMessageBus.Dispose(); // Sends all the delayed messages + return summary; + } + + diagnosticMessageSink.OnMessage(new DiagnosticMessage("Execution of '{0}' failed (attempt #{1}), retrying...", DisplayName, runCount)); + Xamarin.Forms.Mocks.MockForms.Init(); + Prism.Navigation.PageNavigationRegistry.ClearRegistrationCache(); + GC.Collect(); + await Task.Delay(100); + } + } + + public override void Serialize(IXunitSerializationInfo data) + { + base.Serialize(data); + + data.AddValue("MaxRetries", maxRetries); + } + + public override void Deserialize(IXunitSerializationInfo data) + { + base.Deserialize(data); + + maxRetries = data.GetValue("MaxRetries"); + } + } +} \ No newline at end of file diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Mocks/AppMock.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Mocks/AppMock.cs index 9d3a349..2757d02 100644 --- a/tests/Prism.Plugin.Popups.Tests.Shared/Mocks/AppMock.cs +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Mocks/AppMock.cs @@ -2,6 +2,7 @@ using Prism.Plugin.Popups.Tests.Mocks.Views; using Prism.Navigation; using Xamarin.Forms; +using Prism.Logging; #if AUTOFAC using Prism.Autofac; #elif DRYIOC @@ -20,9 +21,13 @@ public AppMock(IPlatformInitializer platformInitializer) } - protected override void OnInitialized() + protected override async void OnInitialized() { - NavigationService.NavigateAsync("MainPage").ContinueWith(t => { }); + var result = await NavigationService.NavigateAsync("MainPage"); + if(!result.Success) + { + Container.Resolve().Log(result.Exception.ToString(), Category.Exception, Priority.High); + } } protected override void RegisterTypes(IContainerRegistry containerRegistry) diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Prism.Plugin.Popups.Tests.Shared.projitems b/tests/Prism.Plugin.Popups.Tests.Shared/Prism.Plugin.Popups.Tests.Shared.projitems index c29901d..a5f6ed7 100644 --- a/tests/Prism.Plugin.Popups.Tests.Shared/Prism.Plugin.Popups.Tests.Shared.projitems +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Prism.Plugin.Popups.Tests.Shared.projitems @@ -9,6 +9,10 @@ Prism.Plugin.Popups.Tests + + + + diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Tests/NavigationServiceFixture.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Tests/NavigationServiceFixture.cs index 2512841..3b894f2 100644 --- a/tests/Prism.Plugin.Popups.Tests.Shared/Tests/NavigationServiceFixture.cs +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Tests/NavigationServiceFixture.cs @@ -11,6 +11,7 @@ using Xunit; using Xunit.Abstractions; using Prism.Navigation; +using Prism.Common; #if AUTOFAC namespace Prism.Plugin.Popups.Autofac.Tests @@ -27,11 +28,13 @@ public NavigationServiceFixture(ITestOutputHelper testOutputHelper) { } - public void PopupNavigationService_SetsStandardPages() [Retry] + public async Task PopupNavigationService_SetsStandardPages() { var app = GetApp(); Assert.Empty(PopupNavigation.Instance.PopupStack); + await Task.Delay(150); + Assert.NotNull(app.MainPage); Assert.IsType(app.MainPage); } diff --git a/tests/Prism.Plugin.Popups.Tests.Shared/Tests/RegistrationFixture.cs b/tests/Prism.Plugin.Popups.Tests.Shared/Tests/RegistrationFixture.cs index 29d0abf..2e93c95 100644 --- a/tests/Prism.Plugin.Popups.Tests.Shared/Tests/RegistrationFixture.cs +++ b/tests/Prism.Plugin.Popups.Tests.Shared/Tests/RegistrationFixture.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; using Xunit; using Xunit.Abstractions; @@ -67,10 +68,12 @@ public void Application_Has_PopupNavigationService() Assert.IsType(app.GetNavigationService()); } - public void MainPage_Has_PopupNavigationService() [Retry] + public async Task MainPage_Has_PopupNavigationService() { var app = GetApp(); + await Task.Delay(150); + var vm = app.MainPage.BindingContext as MainPageViewModel; Assert.NotNull(vm); Assert.NotNull(vm.NavigationService); diff --git a/tests/Prism.Plugin.Popups.Unity.Tests/Prism.Plugin.Popups.Unity.Tests.csproj b/tests/Prism.Plugin.Popups.Unity.Tests/Prism.Plugin.Popups.Unity.Tests.csproj index 51b264e..0bb3f3b 100644 --- a/tests/Prism.Plugin.Popups.Unity.Tests/Prism.Plugin.Popups.Unity.Tests.csproj +++ b/tests/Prism.Plugin.Popups.Unity.Tests/Prism.Plugin.Popups.Unity.Tests.csproj @@ -16,7 +16,6 @@ all runtime; build; native; contentfiles; analyzers -