public async Task KeepingItCleanWithParallelTasks() { const int expectedResult = 42; const string expectedCleanResult = "Forty Two"; var mock = Substitute.For <IControlCandidateTask <int, string> >(); mock.Control().Returns(expectedResult); mock.Candidate().Returns(0); mock.Clean(expectedResult).Returns(expectedCleanResult); var result = await Scientist.ScienceAsync <int, string>(nameof(KeepingItCleanWithParallelTasks), experiment => { experiment.Use(mock.Control); experiment.Try(mock.Candidate); experiment.Clean(mock.Clean); }); Assert.Equal(expectedResult, result); // Make sure that the observations aren't cleaned unless called explicitly. mock.DidNotReceive().Clean(expectedResult); Assert.Equal( expectedCleanResult, TestHelper.Results <int, string>(nameof(KeepingItCleanWithParallelTasks)).First().Control.CleanedValue); mock.Received().Clean(expectedResult); }
public async Task <string[]> GetValues(Guid correlationId) { var result = await Scientist.ScienceAsync <string[]>("upstream-api", experiment => { experiment.Use(async() => { using (_metrics.Measure.Timer.Time(_apiToUseTimerOptions)) { return(await _apiToUseClient.GetValues(correlationId)); } }); experiment.Try(async() => { using (_metrics.Measure.Timer.Time(_apiToTryTimerOptions)) { return(await _apiToTestClient.GetValues(correlationId)); } }); experiment.Compare((a, b) => { _logger.LogInformation("{@CorrelationId} Sets: {@a} - {@b}", correlationId, a, b); return(a.SequenceEqual(b)); }); }); return(result); }
public async Task OnPost() { HasResult = true; Position = FibonacciInput.Position; Result = await Scientist.ScienceAsync <int>("fibonacci-implementation", experiment => { experiment.Use(async() => await _recursiveFibonacciCalculator.CalculateAsync(Position)); experiment.Try(async() => await _linearFibonacciCalculator.CalculateAsync(Position)); experiment.AddContext("Position", Position); }); LastResults = _experimentResultsGetter.LastResults; OverallResults = _experimentResultsGetter.OverallResults; }
public async Task RunsBothBranchesOfTheExperimentAndThrowsCorrectInnerException() { var mock = Substitute.For <IControlCandidate <Task <int> > >(); var controlException = new InvalidOperationException(null, new Exception()); var candidateException = new InvalidOperationException(null, new Exception()); mock.Control().Returns <Task <int> >(x => { throw controlException; }); mock.Candidate().Returns <Task <int> >(x => { throw controlException; }); const string experimentName = nameof(RunsBothBranchesOfTheExperimentAndThrowsCorrectInnerException); await Assert.ThrowsAsync <InvalidOperationException>(async() => { await Scientist.ScienceAsync <int>(experimentName, experiment => { experiment.Use(mock.Control); experiment.Try("candidate", mock.Candidate); }); }); }
public async Task RunsBothBranchesOfTheExperimentAsyncAndReportsFailure() { bool candidateRan = false; bool controlRan = false; // We introduce side effects for testing. Don't do this in real life please. Func <Task <int> > control = () => { controlRan = true; return(Task.FromResult(42)); }; Func <Task <int> > candidate = () => { candidateRan = true; return(Task.FromResult(43)); }; var result = await Scientist.ScienceAsync <int>("failure", experiment => { experiment.Use(control); experiment.Try(candidate); }); Assert.Equal(42, result); Assert.True(candidateRan); Assert.True(controlRan); Assert.False(TestHelper.Observation.First(m => m.Name == "failure").Success); }
public async Task RunsBothBranchesOfTheExperimentAsyncAndReportsFailure() { var mock = Substitute.For <IControlCandidateTask <int> >(); mock.Control().Returns(Task.FromResult(42)); mock.Candidate().Returns(Task.FromResult(43)); const string experimentName = nameof(RunsBothBranchesOfTheExperimentAsyncAndReportsFailure); var result = await Scientist.ScienceAsync <int>(experimentName, experiment => { experiment.Use(mock.Control); experiment.Try("candidate", mock.Candidate); }); Assert.Equal(42, result); await mock.Received().Control(); await mock.Received().Candidate(); Assert.False(TestHelper.Results <int>(experimentName).First().Matched); }
public async Task RunsBothBranchesOfTheExperimentAsyncAndReportsFailure() { var mock = Substitute.For <IControlCandidateTask <int> >(); mock.Control().Returns(Task.FromResult(42)); mock.Candidate().Returns(Task.FromResult(43)); var result = await Scientist.ScienceAsync <int>("failure", experiment => { experiment.Use(mock.Control); experiment.Try(mock.Candidate); }); Assert.Equal(42, result); await mock.Received().Control(); await mock.Received().Candidate(); Assert.False(TestHelper.Observation.First(m => m.Name == "failure").Success); }
public void ThrowsArgumentExceptionWhenConcurrentTasksInvalid() { var mock = Substitute.For <IControlCandidateTask <int> >(); mock.Control().Returns(x => 1); mock.Candidate().Returns(x => 2); const string experimentName = nameof(ThrowsArgumentExceptionWhenConcurrentTasksInvalid); var ex = Assert.Throws <ArgumentException>(() => { Scientist.ScienceAsync <int>(experimentName, 0, experiment => { experiment.Use(mock.Control); experiment.Try(mock.Candidate); }); }); Exception baseException = ex.GetBaseException(); Assert.IsType <ArgumentException>(baseException); mock.DidNotReceive().Control(); mock.DidNotReceive().Candidate(); }
public async Task RunsTasksConcurrently(int concurrentTasks) { // Control + 3 experiments var totalTasks = 1 + 3; // Expected number of batches var expectedBatches = Math.Ceiling(1D * totalTasks / concurrentTasks); // Use CountdownEvents to ensure tasks don't finish before all tasks in that batch have started var startedSignal = new CountdownEvent(concurrentTasks); var finishedSignal = new CountdownEvent(concurrentTasks); // Batch counter int batch = 1; // Our test task var task = new Func <Task <KeyValuePair <int, int> > >(() => { return(Task.Run(() => { // Signal that we have started var last = startedSignal.Signal(); var myBatch = batch; // Wait till all tasks for this batch have started startedSignal.Wait(); // Signal we have finished finishedSignal.Signal(); // Last task to start needs to reset the events if (last) { // Wait for all tasks in the batch to have finished finishedSignal.Wait(); // Reset the countdown events startedSignal.Reset(); finishedSignal.Reset(); batch++; } // Return threadId return new KeyValuePair <int, int>(myBatch, Thread.CurrentThread.ManagedThreadId); })); }); // Run the experiment string experimentName = nameof(RunsTasksConcurrently) + concurrentTasks; await Scientist.ScienceAsync <KeyValuePair <int, int> >(experimentName, concurrentTasks, experiment => { // Add our control and experiments experiment.Use(task); for (int idx = 2; idx <= totalTasks; idx++) { experiment.Try($"experiment{idx}", task); } }); // Get the test result var result = TestHelper.Results <KeyValuePair <int, int> >(experimentName).First(); // Consolidate the returned values from the tasks var results = result.Observations.Select(x => x.Value); // Assert correct number of batches Assert.Equal(expectedBatches, results.Select(x => x.Key).Distinct().Count()); // Now check each batch for (int batchNo = 1; batchNo <= expectedBatches; batchNo++) { // Get the threadIds used by each task in the batch var batchThreadIds = results.Where(x => x.Key == batchNo).Select(x => x.Value); // Assert expected number of concurrent tasks in batch Assert.Equal(concurrentTasks, batchThreadIds.Count()); // Assert unique threadIds in batch Assert.Equal(batchThreadIds.Count(), batchThreadIds.Distinct().Count()); } }