public async Task HandlesExceptionsThrownImmediatelyByInnerPublisher()
    {
        const int expectedResult   = 42;
        var       exceptionToThrow = new PublishException();
        var       exceptionsThrown = new List <Exception>();

        var innerPublisher = Substitute.For <IResultPublisher>();

        innerPublisher.Publish(Arg.Any <Result <int, int> >())
        .Throws(exceptionToThrow);

        var fireAndForgetPublisher = new FireAndForgetResultPublisher(innerPublisher, ex => { exceptionsThrown.Add(ex); });

        var mock = Substitute.For <IControlCandidate <int, string> >();

        mock.Control().Returns(expectedResult);
        mock.Candidate().Returns(expectedResult);

        using (Swap.Publisher(fireAndForgetPublisher))
        {
            var result = Scientist.Science <int>("myExperiment", experiment =>
            {
                experiment.Use(mock.Control);
                experiment.Try(mock.Candidate);
            });
        }

        await fireAndForgetPublisher.WhenPublished();

        Assert.Equal(new List <Exception> {
            exceptionToThrow
        }, exceptionsThrown);
    }
    public async Task HandlesExceptionsThrownImmediatelyByInnerPublisher()
    {
        const int expectedResult = 42;
        var exceptionToThrow = new PublishException();
        var exceptionsThrown = new List<Exception>();

        var innerPublisher = Substitute.For<IResultPublisher>();
        innerPublisher.Publish(Arg.Any<Result<int, int>>())
            .Throws(exceptionToThrow);

        var fireAndForgetPublisher = new FireAndForgetResultPublisher(innerPublisher, ex => { exceptionsThrown.Add(ex); });

        var mock = Substitute.For<IControlCandidate<int, string>>();
        mock.Control().Returns(expectedResult);
        mock.Candidate().Returns(expectedResult);

        using (Swap.Publisher(fireAndForgetPublisher))
        {
            var result = Scientist.Science<int>("myExperiment", experiment =>
            {
                experiment.Use(mock.Control);
                experiment.Try(mock.Candidate);
            });
        }

        await fireAndForgetPublisher.WhenPublished();

        Assert.Equal(new List<Exception> { exceptionToThrow }, exceptionsThrown);
    }
    public async Task PublishesAsynchronously()
    {
        const int expectedResult     = 42;
        var       pendingPublishTask = new TaskCompletionSource <object>();

        // Create a new publisher that will delay all
        // publishing to account for this test.
        var innerPublisher = Substitute.For <IResultPublisher>();

        innerPublisher.Publish(Arg.Any <Result <int, int> >())
        .Returns(call => pendingPublishTask.Task);

        var fireAndForgetPublisher = new FireAndForgetResultPublisher(innerPublisher, ex => { });

        var mock = Substitute.For <IControlCandidate <int, string> >();

        mock.Control().Returns(expectedResult);
        mock.Candidate().Returns(expectedResult);

        const int count = 10;

        using (Swap.Publisher(fireAndForgetPublisher))
        {
            Parallel.ForEach(
                Enumerable.Repeat(0, count),
                src =>
            {
                var result = Scientist.Science <int>("myExperiment", experiment =>
                {
                    experiment.Use(mock.Control);
                    experiment.Try(mock.Candidate);
                });

                Assert.Equal(expectedResult, result);
            });
        }

        // Make sure that the above science calls are still publishing.
        Task whenPublished = fireAndForgetPublisher.WhenPublished();

        Assert.NotNull(whenPublished);

        // Ensure that the mock was called before the when published task has completed.
        mock.Received(count).Control();
        mock.Received(count).Candidate();

        Assert.False(whenPublished.IsCompleted, "When Published Task completed early.");

        pendingPublishTask.SetResult(null);

        await whenPublished;

        Assert.True(whenPublished.IsCompleted, "When Published Task isn't complete.");
    }
    public async Task PublishesAsynchronously()
    {
        const int expectedResult = 42;
        var pendingPublishTask = new TaskCompletionSource<object>();

        // Create a new publisher that will delay all
        // publishing to account for this test.
        var innerPublisher = Substitute.For<IResultPublisher>();
        innerPublisher.Publish(Arg.Any<Result<int, int>>())
            .Returns(call => pendingPublishTask.Task);

        var fireAndForgetPublisher = new FireAndForgetResultPublisher(innerPublisher, ex => { });

        var mock = Substitute.For<IControlCandidate<int, string>>();
        mock.Control().Returns(expectedResult);
        mock.Candidate().Returns(expectedResult);

        const int count = 10;
        using (Swap.Publisher(fireAndForgetPublisher))
        {
            Parallel.ForEach(
                Enumerable.Repeat(0, count),
                src =>
                {
                    var result = Scientist.Science<int>("myExperiment", experiment =>
                    {
                        experiment.Use(mock.Control);
                        experiment.Try(mock.Candidate);
                    });

                    Assert.Equal(expectedResult, result);
                });
        }

        // Make sure that the above science calls are still publishing.
        Task whenPublished = fireAndForgetPublisher.WhenPublished();
        Assert.NotNull(whenPublished);

        // Ensure that the mock was called before the when published task has completed.
        mock.Received(count).Control();
        mock.Received(count).Candidate();

        Assert.False(whenPublished.IsCompleted, "When Published Task completed early.");

        pendingPublishTask.SetResult(null);

        await whenPublished;

        Assert.True(whenPublished.IsCompleted, "When Published Task isn't complete.");
    }
    public async Task HandlesDelayedExceptionsThrownByInnerPublisher()
    {
        const int expectedResult   = 42;
        var       exceptionToThrow = new PublishException();
        var       exceptionsThrown = new List <Exception>();

        var pendingPublishTask = new TaskCompletionSource <object>();
        var innerPublisher     = Substitute.For <IResultPublisher>();

        innerPublisher.Publish(Arg.Any <Result <int, int> >())
        .Returns(call => pendingPublishTask.Task.ContinueWith(_ => { throw exceptionToThrow; }));

        var fireAndForgetPublisher = new FireAndForgetResultPublisher(innerPublisher, ex => { exceptionsThrown.Add(ex); });

        var mock = Substitute.For <IControlCandidate <int, string> >();

        mock.Control().Returns(expectedResult);
        mock.Candidate().Returns(expectedResult);

        using (Swap.Publisher(fireAndForgetPublisher))
        {
            var result = Scientist.Science <int>("myExperiment", experiment =>
            {
                experiment.Use(mock.Control);
                experiment.Try(mock.Candidate);
            });
        }

        var whenPublished = fireAndForgetPublisher.WhenPublished();

        Assert.False(whenPublished.IsCompleted, "When Published Task completed early.");

        pendingPublishTask.SetResult(null);

        await whenPublished;

        Assert.True(whenPublished.IsCompleted, "When Published Task isn't complete.");

        Assert.Equal(new List <Exception> {
            exceptionToThrow
        }, exceptionsThrown);
    }
    public async Task HandlesDelayedExceptionsThrownByInnerPublisher()
    {
        const int expectedResult = 42;
        var exceptionToThrow = new PublishException();
        var exceptionsThrown = new List<Exception>();

        var pendingPublishTask = new TaskCompletionSource<object>();
        var innerPublisher = Substitute.For<IResultPublisher>();
        innerPublisher.Publish(Arg.Any<Result<int, int>>())
            .Returns(call => pendingPublishTask.Task.ContinueWith(_ => { throw exceptionToThrow; }));

        var fireAndForgetPublisher = new FireAndForgetResultPublisher(innerPublisher, ex => { exceptionsThrown.Add(ex); });

        var mock = Substitute.For<IControlCandidate<int, string>>();
        mock.Control().Returns(expectedResult);
        mock.Candidate().Returns(expectedResult);

        using (Swap.Publisher(fireAndForgetPublisher))
        {
            var result = Scientist.Science<int>("myExperiment", experiment =>
            {
                experiment.Use(mock.Control);
                experiment.Try(mock.Candidate);
            });
        }

        var whenPublished = fireAndForgetPublisher.WhenPublished();

        Assert.False(whenPublished.IsCompleted, "When Published Task completed early.");

        pendingPublishTask.SetResult(null);

        await whenPublished;

        Assert.True(whenPublished.IsCompleted, "When Published Task isn't complete.");

        Assert.Equal(new List<Exception> { exceptionToThrow }, exceptionsThrown);
    }