/// <summary> /// Initializes a new instance of the <see cref="TaskAwaiter"/> struct. /// </summary> private TaskAwaiter(SystemTask awaitedTask, ref SystemCompiler.TaskAwaiter awaiter) { this.AwaitedTask = awaitedTask; this.Awaiter = awaiter; RuntimeProvider.TryGetFromSynchronizationContext(out CoyoteRuntime runtime); this.Runtime = runtime; }
/// <summary> /// Initializes a new instance of the <see cref="ConfiguredTaskAwaiter"/> struct. /// </summary> internal ConfiguredTaskAwaiter(CoyoteRuntime runtime, SystemTasks.Task awaitedTask, bool continueOnCapturedContext) { this.Runtime = runtime; this.AwaitedTask = awaitedTask; this.Awaiter = awaitedTask.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); }
/// <summary> /// Initializes a new instance of the <see cref="TaskAwaiter"/> struct. /// </summary> internal TaskAwaiter(SystemTask awaitedTask) { this.AwaitedTask = awaitedTask; this.Awaiter = awaitedTask.GetAwaiter(); RuntimeProvider.TryGetFromSynchronizationContext(out CoyoteRuntime runtime); this.Runtime = runtime; }
/// <summary> /// Initializes a new instance of the <see cref="SpecificationEngine"/> class. /// </summary> internal SpecificationEngine(Configuration configuration, CoyoteRuntime runtime) { this.Configuration = configuration; this.Runtime = runtime; this.LivenessMonitors = new List <TaskLivenessMonitor>(); this.StateMachineMonitors = new List <Monitor>(); this.IsMonitoringEnabled = runtime.IsControlled || configuration.IsMonitoringEnabledInInProduction; }
/// <summary> /// Initializes a new instance of the <see cref="ValueTaskAwaiter"/> struct. /// </summary> private ValueTaskAwaiter(ref SystemValueTask awaitedTask, ref SystemCompiler.ValueTaskAwaiter awaiter) { this.AwaitedTask = ValueTaskAwaiter.TryGetTask(ref awaitedTask, out Task innerTask) ? innerTask : null; this.Awaiter = awaiter; RuntimeProvider.TryGetFromSynchronizationContext(out CoyoteRuntime runtime); this.Runtime = runtime; }
/// <summary> /// Initializes a new instance of the <see cref="ValueTaskAwaiter"/> struct. /// </summary> internal ValueTaskAwaiter(ref SystemValueTask awaitedTask) { this.AwaitedTask = ValueTaskAwaiter.TryGetTask(ref awaitedTask, out Task innerTask) ? innerTask : null; this.Awaiter = awaitedTask.GetAwaiter(); RuntimeProvider.TryGetFromSynchronizationContext(out CoyoteRuntime runtime); this.Runtime = runtime; }
public CoyoteTasks.Task ScheduleAction(Action action, Task predecessor, CancellationToken cancellationToken) { // TODO: support cancellations during testing. this.Assert(action != null, "The task cannot execute a null action."); ulong operationId = this.Runtime.GetNextOperationId(); var op = new TaskOperation(operationId, this.Scheduler); this.Scheduler.RegisterOperation(op); op.OnEnabled(); var task = new Task(() => { try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); OperationScheduler.StartOperation(op); if (predecessor != null) { op.OnWaitTask(predecessor); } action(); } catch (Exception ex) { // Report the unhandled exception unless it is our ExecutionCanceledException which is our // way of terminating async task operations at the end of the test iteration. if (!(ex is ExecutionCanceledException)) { this.ReportUnhandledExceptionInOperation(op, ex); } // and rethrow it throw; } finally { IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId); op.OnCompleted(); } }); // Schedule a task continuation that will schedule the next enabled operation upon completion. task.ContinueWith(t => this.Scheduler.ScheduleNextEnabledOperation(), TaskScheduler.Current); IO.Debug.WriteLine("<CreateLog> Operation '{0}' was created to execute task '{1}'.", op.Name, task.Id); this.Scheduler.ScheduleOperation(op, task.Id); task.Start(); this.Scheduler.WaitOperationStart(op); this.Scheduler.ScheduleNextEnabledOperation(); return(new CoyoteTasks.Task(this, task)); }
public CoyoteTasks.Task <TResult> ScheduleFunction <TResult>(Func <CoyoteTasks.Task <TResult> > function, Task predecessor, CancellationToken cancellationToken) { // TODO: support cancellations during testing. this.Assert(function != null, "The task cannot execute a null function."); ulong operationId = this.Runtime.GetNextOperationId(); var op = new TaskOperation(operationId, this.Scheduler); this.Scheduler.RegisterOperation(op); op.OnEnabled(); var task = new Task <Task <TResult> >(() => { try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); OperationScheduler.StartOperation(op); if (predecessor != null) { op.OnWaitTask(predecessor); } CoyoteTasks.Task <TResult> resultTask = function(); this.OnWaitTask(operationId, resultTask.UncontrolledTask); return(resultTask.UncontrolledTask); } catch (Exception ex) { // Report the unhandled exception and rethrow it. this.ReportUnhandledExceptionInOperation(op, ex); throw; } finally { IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId); op.OnCompleted(); } }); Task <TResult> innerTask = task.Unwrap(); // Schedule a task continuation that will schedule the next enabled operation upon completion. innerTask.ContinueWith(t => this.Scheduler.ScheduleNextEnabledOperation(), TaskScheduler.Current); IO.Debug.WriteLine("<CreateLog> Operation '{0}' was created to execute task '{1}'.", op.Name, task.Id); this.Scheduler.ScheduleOperation(op, task.Id); task.Start(); this.Scheduler.WaitOperationStart(op); this.Scheduler.ScheduleNextEnabledOperation(); return(new CoyoteTasks.Task <TResult>(this, innerTask)); }
public void IncrementCountTest() { ICoyoteRuntime runtime = new CoyoteRuntime(); var viewmodel = new SecondCoyoteLibrary.Pages.CounterViewModel(runtime); viewmodel.CurrentCount = 10; viewmodel.IncrementAmount = 3; viewmodel.IncrementCount(); Assert.AreEqual(13, viewmodel.CurrentCount); }
/// <summary> /// Initializes a new instance of the <see cref="ConfiguredTaskAwaiter"/> struct. /// </summary> internal ConfiguredTaskAwaiter(SystemTask awaitedTask, bool continueOnCapturedContext) { if (RuntimeProvider.TryGetFromSynchronizationContext(out CoyoteRuntime runtime)) { // Force the continuation to run on the current context so that it can be controlled. continueOnCapturedContext = true; } this.AwaitedTask = awaitedTask; this.Awaiter = awaitedTask.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); this.Runtime = runtime; }
/// <summary> /// Creates an instance of the <see cref="AsyncTaskMethodBuilder"/> struct. /// </summary> public static AsyncTaskMethodBuilder Create() { CoyoteRuntime runtime = null; if (SynchronizationContext.Current is ControlledSynchronizationContext controlledContext && controlledContext.Runtime.SchedulingPolicy != SchedulingPolicy.None) { runtime = controlledContext.Runtime; } return(new AsyncTaskMethodBuilder(runtime)); }
public async Task IncrementCountTest() { SecondCoyoteLibrary.ICoyoteRuntime runtime = new CoyoteRuntime(); var viewmodel = new SecondCoyoteLibrary.Pages.CounterViewModel(runtime); viewmodel.CurrentCount = 10; viewmodel.IncrementAmount = 3; //var task = await viewmodel.IncrementCount(); //task.Wait(); Assert.Equal(13, viewmodel.CurrentCount); }
/// <summary> /// Initializes a new instance of the <see cref="ConfiguredTaskAwaiter"/> struct. /// </summary> internal ConfiguredTaskAwaiter(CoyoteRuntime runtime, SystemTasks.Task awaitedTask, bool continueOnCapturedContext) { if (runtime?.SchedulingPolicy != SchedulingPolicy.None) { // Force the continuation to run on the current context so that it can be controlled. continueOnCapturedContext = true; } this.Runtime = runtime; this.AwaitedTask = awaitedTask; this.Awaiter = awaitedTask.ConfigureAwait(continueOnCapturedContext).GetAwaiter(); }
/// <summary> /// Gathers the exploration strategy statistics from the specified runtimne. /// </summary> private void GatherTestingStatistics(CoyoteRuntime runtime) { TestReport report = new TestReport(this.Configuration); runtime.PopulateTestReport(report); var coverageInfo = runtime.DefaultActorExecutionContext.BuildCoverageInfo(); report.CoverageInfo.Merge(coverageInfo); this.TestReport.Merge(report); // Save the DGML graph of the execution path explored in the last iteration. this.LastExecutionGraph = runtime.DefaultActorExecutionContext.GetExecutionGraph(); }
/// <summary> /// Creates a new <see cref="HttpOperation"/> from the specified parameters. /// </summary> #pragma warning disable CA1801 // Parameter not used internal static HttpOperation Create(HttpMethod method, string path, CoyoteRuntime runtime, ControlledOperation source) #pragma warning restore CA1801 // Parameter not used { ulong operationId = runtime.GetNextOperationId(); var op = new HttpOperation(operationId, method, path); runtime.RegisterOperation(op); if (runtime.GetExecutingOperation() is null) { op.IsSourceUncontrolled = true; } return(op); }
/// <summary> /// Initializes a new instance of the <see cref="OperationScheduler"/> class. /// </summary> internal OperationScheduler(CoyoteRuntime runtime, SchedulingStrategy strategy, ScheduleTrace trace, Configuration configuration) { this.Configuration = configuration; this.Runtime = runtime; this.Strategy = strategy; this.OperationMap = new Dictionary <ulong, AsyncOperation>(); this.ScheduleTrace = trace; this.SyncObject = new object(); this.CompletionSource = new TaskCompletionSource <bool>(); this.IsProgramExecuting = true; this.IsAttached = true; this.BugFound = false; this.HasFullyExploredSchedule = false; }
/// <summary> /// Constructs a reproducable trace. /// </summary> private void ConstructReproducibleTrace(CoyoteRuntime runtime) { StringBuilder stringBuilder = new StringBuilder(); if (this.Strategy.IsFair()) { stringBuilder.Append("--fair-scheduling").Append(Environment.NewLine); } if (this.Configuration.IsLivenessCheckingEnabled) { stringBuilder.Append("--liveness-temperature-threshold:" + this.Configuration.LivenessTemperatureThreshold). Append(Environment.NewLine); } if (!string.IsNullOrEmpty(this.Configuration.TestMethodName)) { stringBuilder.Append("--test-method:" + this.Configuration.TestMethodName). Append(Environment.NewLine); } for (int idx = 0; idx < runtime.Scheduler.ScheduleTrace.Count; idx++) { ScheduleStep step = runtime.Scheduler.ScheduleTrace[idx]; if (step.Type == ScheduleStepType.SchedulingChoice) { stringBuilder.Append($"({step.ScheduledOperationId})"); } else if (step.BooleanChoice != null) { stringBuilder.Append(step.BooleanChoice.Value); } else { stringBuilder.Append(step.IntegerChoice.Value); } if (idx < runtime.Scheduler.ScheduleTrace.Count - 1) { stringBuilder.Append(Environment.NewLine); } } this.ReproducibleTrace = stringBuilder.ToString(); }
/// <summary> /// Gathers the exploration strategy statistics from the specified runtimne. /// </summary> private void GatherTestingStatistics(CoyoteRuntime runtime) { TestReport report = this.GetSchedulerReport(runtime.Scheduler); if (this.Configuration.ReportActivityCoverage) { report.CoverageInfo.CoverageGraph = this.Graph; } var coverageInfo = runtime.DefaultActorExecutionContext.BuildCoverageInfo(); report.CoverageInfo.Merge(coverageInfo); this.TestReport.Merge(report); // Also save the graph snapshot of the last iteration, if there is one. this.Graph = coverageInfo.CoverageGraph; }
public CoyoteTasks.Task <TResult> ScheduleDelegate <TResult>(Delegate work, Task predecessor, CancellationToken cancellationToken) { // TODO: support cancellations during testing. this.Assert(work != null, "The task cannot execute a null delegate."); ulong operationId = this.Runtime.GetNextOperationId(); var op = new TaskOperation(operationId, this.Scheduler); this.Scheduler.RegisterOperation(op); op.OnEnabled(); var task = new Task <TResult>(() => { try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); OperationScheduler.StartOperation(op); if (predecessor != null) { op.OnWaitTask(predecessor); } if (work is Func <Task> funcWithTaskResult) { Task resultTask = funcWithTaskResult(); this.OnWaitTask(operationId, resultTask); if (resultTask is TResult typedResultTask) { return(typedResultTask); } } else if (work is Func <Task <TResult> > funcWithGenericTaskResult) { Task <TResult> resultTask = funcWithGenericTaskResult(); this.OnWaitTask(operationId, resultTask); return(resultTask.Result); } else if (work is Func <TResult> funcWithGenericResult) { return(funcWithGenericResult()); } return(default);
/// <summary> /// Gathers the exploration strategy statistics from the specified runtimne. /// </summary> private void GatherTestingStatistics(CoyoteRuntime runtime) { runtime.GetSchedulingStatisticsAndResults(out bool isBugFound, out string bugReport, out int steps, out bool isMaxStepsReached, out bool isScheduleFair, out Exception unhandledException); TestReport report = TestReport.CreateTestReportFromStats(this.Configuration, isBugFound, bugReport, steps, isMaxStepsReached, isScheduleFair, unhandledException); if (this.Configuration.ReportActivityCoverage) { report.CoverageInfo.CoverageGraph = this.Graph; } var coverageInfo = runtime.DefaultActorExecutionContext.BuildCoverageInfo(); report.CoverageInfo.Merge(coverageInfo); this.TestReport.Merge(report); // Also save the graph snapshot of the last iteration, if there is one. this.Graph = coverageInfo.CoverageGraph; }
/// <summary> /// Initializes a new instance of the <see cref="TaskDelayOperation"/> class. /// </summary> internal TaskDelayOperation(ulong operationId, string name, uint delay, CoyoteRuntime runtime) : base(operationId, name, runtime) { this.Timeout = delay > int.MaxValue ? int.MaxValue : (int)delay; }
/// <summary> /// Initializes a new instance of the <see cref="ConfiguredTaskAwaitable"/> struct. /// </summary> internal ConfiguredTaskAwaitable(CoyoteRuntime runtime, SystemTasks.Task awaitedTask, bool continueOnCapturedContext) { this.Awaiter = new ConfiguredTaskAwaiter(runtime, awaitedTask, continueOnCapturedContext); }
internal Task(CoyoteRuntime runtime, SystemTasks.Task <TResult> task) : base(runtime, task) { }
/// <summary> /// Initializes a new instance of the <see cref="TaskAwaiter"/> struct. /// </summary> internal TaskAwaiter(CoyoteRuntime runtime, SystemTasks.Task awaitedTask) { this.Runtime = runtime; this.AwaitedTask = awaitedTask; this.Awaiter = awaitedTask.GetAwaiter(); }
/// <summary> /// Initializes a new instance of the <see cref="TaskAwaiter"/> struct. /// </summary> internal TaskAwaiter(CoyoteRuntime runtime, SystemCompiler.TaskAwaiter awaiter) { this.Runtime = runtime; this.AwaitedTask = null; this.Awaiter = awaiter; }
/// <summary> /// Initializes a new instance of the <see cref="AsyncTaskMethodBuilder"/> struct. /// </summary> private AsyncTaskMethodBuilder(CoyoteRuntime runtime) { this.Runtime = runtime; this.MethodBuilder = default; }
internal Task(CoyoteRuntime runtime, SystemTasks.Task task) { this.Runtime = runtime; this.InternalTask = task ?? throw new ArgumentNullException(nameof(task)); }
/// <summary> /// Execute the operation with the specified context. /// </summary> private void ExecuteOperation(object state) { // Extract the expected operation context from the task state. var context = state as OperationContext <Action, object>; TaskOperation op = context.Operation; CancellationToken ct = context.CancellationToken; Exception exception = null; try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); // Notify the scheduler that the operation started. This will yield execution until // the operation is ready to get scheduled. this.Scheduler.StartOperation(op); if (context.Predecessor != null) { // If there is a predecessor task, then wait until the predecessor completes. ct.ThrowIfCancellationRequested(); op.OnWaitTask(context.Predecessor); } if (context.Options.HasFlag(OperationExecutionOptions.YieldAtStart)) { // Try yield execution to the next operation. this.Scheduler.ScheduleNextOperation(true); } // Check if the operation must be canceled before starting the work. ct.ThrowIfCancellationRequested(); // Start executing the work. context.Work(); } catch (Exception ex) { if (context.Options.HasFlag(OperationExecutionOptions.FailOnException)) { this.Assert(false, "Unhandled exception. {0}", ex); } else { // Unwrap and cache the exception to propagate it. exception = ControlledRuntime.UnwrapException(ex); this.ReportThrownException(exception); } } finally { IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId); op.OnCompleted(); // Set the result task completion source to notify to the awaiters that the operation // has been completed, and schedule the next enabled operation. this.SetTaskCompletionSource(context.ResultSource, null, exception, default); this.Scheduler.ScheduleNextOperation(); } }
/// <summary> /// Execute the (asynchronous) operation with the specified context. /// </summary> private TResult ExecuteOperation <TWork, TExecutor, TResult>(object state) { // Extract the expected operation context from the task state. var context = state as AsyncOperationContext <TWork, TExecutor, TResult> ?? state as OperationContext <TWork, TResult>; TaskOperation op = context.Operation; CancellationToken ct = context.CancellationToken; TResult result = default; Exception exception = null; // The operation execution logic uses two task completion sources: (1) an executor TCS and (2) a result TCS. // We do this to model the execution of tasks in the .NET runtime. For example, the `Task.Factory.StartNew` // method has different semantics from `Task.Run`, e.g. the returned task from `Task.Factory.StartNew(Func<T>)` // completes at the start of an asynchronous operation, so someone cannot await on it for the completion of // the operation. Instead, someone needs to first use `task.Unwrap()`, and then await on the unwrapped task. // To model this, the executor TCS completes at the start of the operation, and contains in its `task.AsyncState` // a reference to the result TCS. This approach allows us to implement `task.Unwrap` in a way that gives access // to the result TCS, which someone can then await for the asynchronous completion of the operation. try { // Update the current asynchronous control flow with the current runtime instance, // allowing future retrieval in the same asynchronous call stack. CoyoteRuntime.AssignAsyncControlFlowRuntime(this.Runtime); // Notify the scheduler that the operation started. This will yield execution until // the operation is ready to get scheduled. this.Scheduler.StartOperation(op); if (context is AsyncOperationContext <TWork, TExecutor, TResult> asyncContext) { // If the operation is asynchronous, then set the executor task completion source, which // can be used by `UnwrapTask` to unwrap and return the task executing this operation. this.SetTaskCompletionSource(asyncContext.ExecutorSource, asyncContext.Executor, null, ct); } if (context.Predecessor != null) { // If there is a predecessor task, then wait until the predecessor completes. ct.ThrowIfCancellationRequested(); op.OnWaitTask(context.Predecessor); } // Check if the operation must be canceled before starting the work. ct.ThrowIfCancellationRequested(); // Start executing the (asynchronous) work. Task executor = null; if (context.Work is Func <Task <TResult> > funcWithTaskResult) { executor = funcWithTaskResult(); } else if (context.Work is Func <Task> funcWithTask) { executor = funcWithTask(); } else if (context.Work is Func <CoyoteTasks.Task <TResult> > funcWithCoyoteTaskResult) { // TODO: temporary until we remove the custom task type. executor = funcWithCoyoteTaskResult().UncontrolledTask; } else if (context.Work is Func <CoyoteTasks.Task> funcWithCoyoteTask) { // TODO: temporary until we remove the custom task type. executor = funcWithCoyoteTask().UncontrolledTask; } else if (context.Work is Func <TResult> func) { result = func(); } else { throw new NotSupportedException($"Unable to execute work with unsupported type {context.Work.GetType()}."); } if (executor != null) { // If the work is asynchronous, then wait until it completes. this.OnWaitTask(op.Id, executor); if (executor.IsFaulted) { // Propagate the failing exception by rethrowing it. ExceptionDispatchInfo.Capture(executor.Exception).Throw(); } else if (executor.IsCanceled) { if (op.Exception != null) { // An exception has been already captured, so propagate it. ExceptionDispatchInfo.Capture(op.Exception).Throw(); } else { // Wait the canceled executor (which is non-blocking as it has already completed) // to throw the generated `OperationCanceledException`. executor.Wait(); } } // Safely get the result without blocking as the work has completed. result = executor is Task <TResult> resultTask ? resultTask.Result : executor is TResult r ? r : default; } } catch (Exception ex) { // Unwrap and cache the exception to propagate it. exception = ControlledRuntime.UnwrapException(ex); this.ReportThrownException(exception); } finally { IO.Debug.WriteLine("<ScheduleDebug> Completed operation '{0}' on task '{1}'.", op.Name, Task.CurrentId); op.OnCompleted(); // Set the result task completion source to notify to the awaiters that the operation // has been completed, and schedule the next enabled operation. this.SetTaskCompletionSource(context.ResultSource, result, exception, default); this.Scheduler.ScheduleNextOperation(); } return(result); }
/// <summary> /// Runs the next testing iteration. /// </summary> private bool RunNextIteration(uint iteration) { if (!this.Strategy.InitializeNextIteration(iteration)) { // The next iteration cannot run, so stop exploring. return(false); } if (!this.IsReplayModeEnabled && this.ShouldPrintIteration(iteration + 1)) { this.Logger.WriteLine(LogSeverity.Important, $"..... Iteration #{iteration + 1}"); // Flush when logging to console. if (this.Logger is ConsoleLogger) { Console.Out.Flush(); } } // Runtime used to serialize and test the program in this iteration. CoyoteRuntime runtime = null; // Logger used to intercept the program output if no custom logger // is installed and if verbosity is turned off. InMemoryLogger runtimeLogger = null; // Gets a handle to the standard output and error streams. var stdOut = Console.Out; var stdErr = Console.Error; try { // Creates a new instance of the controlled runtime. runtime = new CoyoteRuntime(this.Configuration, this.Strategy, this.RandomValueGenerator); // If verbosity is turned off, then intercept the program log, and also redirect // the standard output and error streams to a nul logger. if (!this.Configuration.IsVerbose) { runtimeLogger = new InMemoryLogger(); if (this.Logger != this.DefaultLogger) { runtimeLogger.UserLogger = this.Logger; } runtime.Logger = runtimeLogger; var writer = TextWriter.Null; Console.SetOut(writer); Console.SetError(writer); } else if (this.Logger != this.DefaultLogger) { runtime.Logger = this.Logger; } this.InitializeCustomActorLogging(runtime.DefaultActorExecutionContext); // Runs the test and waits for it to terminate. runtime.RunTest(this.TestMethodInfo.Method, this.TestMethodInfo.Name); runtime.WaitAsync().Wait(); // Invokes the user-specified iteration disposal method. this.TestMethodInfo.DisposeCurrentIteration(); // Invoke the per iteration callbacks, if any. foreach (var callback in this.PerIterationCallbacks) { callback(iteration); } if (!runtime.Scheduler.BugFound) { // Checks for liveness errors. Only checked if no safety errors have been found. runtime.CheckLivenessErrors(); } if (runtime.Scheduler.BugFound) { this.Logger.WriteLine(LogSeverity.Error, runtime.Scheduler.BugReport); } runtime.LogWriter.LogCompletion(); this.GatherTestingStatistics(runtime); if (!this.IsReplayModeEnabled && this.TestReport.NumOfFoundBugs > 0) { if (runtimeLogger != null) { this.ReadableTrace = string.Empty; if (this.Configuration.EnableTelemetry) { this.ReadableTrace += $"<TelemetryLog> Telemetry is enabled, see {LearnAboutTelemetryUrl}.\n"; } this.ReadableTrace += runtimeLogger.ToString(); this.ReadableTrace += this.TestReport.GetText(this.Configuration, "<StrategyLog>"); } this.ConstructReproducibleTrace(runtime); } } finally { if (!this.Configuration.IsVerbose) { // Restores the standard output and error streams. Console.SetOut(stdOut); Console.SetError(stdErr); } if (!this.IsReplayModeEnabled && this.Configuration.PerformFullExploration && runtime.Scheduler.BugFound) { this.Logger.WriteLine(LogSeverity.Important, $"..... Iteration #{iteration + 1} " + $"triggered bug #{this.TestReport.NumOfFoundBugs} " + $"[task-{this.Configuration.TestingProcessId}]"); } // Cleans up the runtime before the next iteration starts. runtimeLogger?.Dispose(); runtime?.Dispose(); } return(true); }