public override void Open() { base.Open(); TapThread.Sleep(50); isOpen = true; Log.Info(Name + " Opened."); }
public override void Run() { // Delay was added so that behavior could be observed in the Timing Analyzer. TapThread.Sleep(100); RunChildSteps(); // If step has child steps. UpgradeVerdict(Verdict.Pass); }
/// <summary> /// Execute the TestPlan as specified. /// </summary> /// <param name="resultListeners">ResultListeners for result outputs.</param> /// <param name="metaDataParameters">Optional metadata parameters.</param> /// <param name="stepsOverride">Sub-section of test plan to be executed. Note this might include child steps of disabled parent steps.</param> /// <param name="cancellationToken">Cancellation token to abort the testplan</param> /// <returns>TestPlanRun results, no StepResults.</returns> public Task <TestPlanRun> ExecuteAsync(IEnumerable <IResultListener> resultListeners, IEnumerable <ResultParameter> metaDataParameters, HashSet <ITestStep> stepsOverride, CancellationToken cancellationToken) { Task <TestPlanRun> result = Task.Run(() => { var sem = new SemaphoreSlim(0); TestPlanRun testPlanRun = null; TapThread.Start(() => { try { cancellationToken.Register(TapThread.Current.Abort); testPlanRun = Execute(resultListeners, metaDataParameters, stepsOverride); } finally { sem.Release(); } }, "Plan Thread"); sem.Wait(); return(testPlanRun); }); return(result); }
/// <summary> /// Execute the TestPlan as specified. Blocking. /// </summary> /// <param name="resultListeners">ResultListeners for result outputs.</param> /// <param name="metaDataParameters">Optional metadata parameters.</param> /// <param name="stepsOverride">Sub-section of test plan to be executed. Note this might include child steps of disabled parent steps.</param> /// <returns>TestPlanRun results, no StepResults.</returns> public TestPlanRun Execute(IEnumerable <IResultListener> resultListeners, IEnumerable <ResultParameter> metaDataParameters = null, HashSet <ITestStep> stepsOverride = null) { TestPlanRun run = null; TapThread.WithNewContext(() => run = this.DoExecute(resultListeners, metaDataParameters, stepsOverride)); return(run); }
public void Run2() { var steps = EnabledChildSteps.ToArray(); SemaphoreSlim sem = new SemaphoreSlim(0); var trd = TapThread.Current; Log.Info("Starting {0} child steps in separate threads.", steps.Length); foreach (var _step in steps) { var step = _step; TapThread.Start(() => { try { RunChildStep(step); } catch { // no need to do anything. This thread will end now. TapThread.WithNewContext(trd.Abort, null); } finally { sem.Release(); } }); } for (int waits = 0; waits < steps.Length; waits++) { sem.Wait(); } }
public void BreakAbortStepRunNull() { TestPlan testPlan = new TestPlan(); SequenceStep step1 = new SequenceStep(); SequenceStep step2 = new SequenceStep(); SequenceStep step3 = new SequenceStep(); testPlan.Steps.Add(step1); testPlan.Steps.Add(step2); testPlan.Steps.Add(step3); TapThread.WithNewContext(() => { var planThread = TapThread.Current; testPlan.BreakOffered += (s, e) => planThread.Abort(); testPlan.Execute(); Assert.IsTrue(TapThread.Current.AbortToken.IsCancellationRequested); }); Assert.IsFalse(TapThread.Current.AbortToken.IsCancellationRequested); foreach (var step in testPlan.Steps) { Assert.IsNull(step.StepRun); } }
/// <summary> /// Start a search task that finds plugins to the platform. /// This call is not blocking, some other calls to PluginManager will automatically /// wait for this task to finish (or even start it if it hasn't been already). These calls /// include <see cref="GetAllPlugins"/>, <see cref="GetPlugins{BaseType}"/>, /// <see cref="GetPlugins(Type)"/>, <see cref="LocateType(string)"/> and <see cref="LocateTypeData(string)"/> /// </summary> public static Task SearchAsync() { searchTask.Reset(); searcher = null; ChangeID++; TapThread.Start(Search); return(Task.Run(() => GetSearcher())); }
// In this method the layout of the dockable panel is defined/setup. // The ITapDockContext enables you to set the TestPlan, attach ResultListeners, // configure Settings and start execution of a TestPlan. public FrameworkElement CreateElement(ITapDockContext context) { var loadPlanBtn = new Button() { Content = "Load Plan" }; var runPlanBtn = new Button() { Content = "Run Plan" }; var stopPlanBtn = new Button() { Content = "Stop Plan" }; var statusTxt = new TextBlock { FontSize = 40, HorizontalAlignment = System.Windows.HorizontalAlignment.Center }; // Setup UI panel and add elements var panel = new StackPanel() { Orientation = System.Windows.Controls.Orientation.Vertical }; panel.Children.Add(loadPlanBtn); panel.Children.Add(runPlanBtn); panel.Children.Add(stopPlanBtn); panel.Children.Add(statusTxt); TapThread planThread = null; // Register event-handling methods for each of the buttons runPlanBtn.Click += (s, e) => planThread = context.Run(); stopPlanBtn.Click += (s, e) => planThread?.Abort(); loadPlanBtn.Click += (s, e) => { var fd = new OpenFileDialog(); fd.CheckFileExists = true; var r = fd.ShowDialog(); try { if (r == DialogResult.OK) { context.Plan = TestPlan.Load(fd.FileName); } } catch (InvalidOperationException ex) { Log.Warning("{0}", ex.Message); } }; // Attach Result listener. runPlanBtn and statusTxt is updated according to status context.ResultListeners.Add(listener = new dockResultListener(runPlanBtn, statusTxt)); return(panel); }
public override void Run() { // There are four levels of log messages Info, Warning, Error, Debug. MyLog.Info("Info from Run"); for (int i = 0; i < 10; i++) { MyLog.Debug("Debug {0} from Run", i); // MyLog.X works like string.Format with regards to arguments. } MyLog.Warning("Warning from Run"); MyLog.Error("Error from Run"); // The Log can accept a Stopwatch Object to be used for timing analysis. Stopwatch sw1 = Stopwatch.StartNew(); TapThread.Sleep(100); MyLog.Info(sw1, "Info from Run"); Stopwatch sw2 = Stopwatch.StartNew(); TapThread.Sleep(200); MyLog.Error(sw2, "Error from step"); // Tracebar can be used to show results in the MyLog. var traceBar = new TraceBar(); traceBar.LowerLimit = -3.0; for (var i = -2; i < 11; i++) { traceBar.UpperLimit = i < 5 ? 3 : 15; // GetBar returns a string with value, low limit, a dashed line // indicating magnitude, the upper limit, and (if failing), a fail indicator. string temp = traceBar.GetBar(i); MyLog.Info("MyResult: " + temp); TapThread.Sleep(200); } // Sample output shown below. // MyResult: 2.00 - 3-------------------------|----- 3 // MyResult: 3.00 - 3------------------------------ | 3 // MyResult: 4.00 - 3------------------------------ > 3 Fail // MyResult: 5.00 - 3------------ -| -----------------15 // MyResult: 6.00 - 3-------------- -| ---------------15 // TraceBar remembers if any results failed, so it can be used for the verdict. UpgradeVerdict(traceBar.CombinedVerdict); // The log also supports showing stack traces. // Useful for debugging. try { throw new Exception("My exception"); } catch (Exception e) { MyLog.Error("Caught exception: '{0}'", e.Message); MyLog.Debug(e); // Prints the stack trace to the MyLog. } }
public override void Run() { TapThread.Sleep((int)(1000 * Duration)); for (double i = 0; i < NResults; i++) { Results.Publish("UnitTest", new List <string> { "Channel", "Power [dBm]" }, Math.Sin(i) /*,Math.Cos(i)*/); } Verdict = Verdict.Pass; }
public override void Run() { TapThread.Sleep(RunDelay * 1000); Results.Defer(() => { TapThread.Sleep(DeferDelay * 1000); // You can also publish results here for example. Results.Publish("MyTestResult", new List <string> { "Test" }, 1); }); }
public override void Run() { for (var i = 0; i < Count; i++) { // Do work. TapThread.Sleep(TimeSpan.FromSeconds(DelaySecs)); // Offer GUI to break at this point. OfferBreak(); } // GUI can automatically break when running its child steps. RunChildSteps(); }
private void MonitorPackageChange() { if (!IsMonitoringPackageChange) { IsMonitoringPackageChange = true; TapThread.Start(() => { while (true) { ChangeId.WaitForChangeBlocking(); PackageChanged(); } }); } }
public void TestThreadField() { tf.Value = 100; tf2.Value = 200; object value = null; Semaphore sem = new Semaphore(0, 1); TapThread.Start(() => { value = tf.Value; sem.Release(); }); sem.WaitOne(); Assert.AreEqual(100, value); }
public override void Run() { Random rand = new Random(); var watch2 = System.Diagnostics.Stopwatch.StartNew(); Log.Info("{2} [{0}/{1}]", 0, Fraction, PreMessage); for (int i = Start; i < Fraction; i += Step) { var watch = System.Diagnostics.Stopwatch.StartNew(); TapThread.Sleep((int)(1000 * Sleep + rand.Next() % 100)); Log.Debug("{2} [{0}/{1}]", i, Fraction, PreMessage); } Log.Debug("{1} [{0}/{0}] Completed", Fraction, PreMessage); Log.Info(watch2, "Simulating progress..."); }
public override void Run() { Results.Defer(() => { TapThread.Sleep(WaitMs); if (Throw) { throw new InvalidOperationException("Intentional"); } else { UpgradeVerdict(Verdict.Error); } }); }
public (string Stdout, string Stderr, int ExitCode) Build() { using (var writer = new StreamWriter(Path.Combine(ProjectBuildTest.WorkingDirectory, Filename))) writer.Write(this.ToString()); if (OperatingSystem.Current != OperatingSystem.Windows) { // // The MSBuild generator is sometimes too eager to skip steps on Linux -- make sure targets are run if (File.Exists(ProjectBuildTest.OutputFile)) { File.Delete(ProjectBuildTest.OutputFile); } } var process = new Process { StartInfo = { FileName = "dotnet", Arguments = $"build {Filename} --verbosity normal", WorkingDirectory = ProjectBuildTest.WorkingDirectory, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true } }; process.Start(); var timeLimit = TimeSpan.FromSeconds(60); var stdout = new StringBuilder(); var stderr = new StringBuilder(); // For some reason, the process doesn't exit properly with WaitForExit while (process.HasExited == false) { if (DateTime.Now - process.StartTime > timeLimit) { break; } TapThread.Sleep(TimeSpan.FromSeconds(1)); } return(process.StandardOutput.ReadToEnd(), process.StandardError.ReadToEnd(), process.ExitCode); }
public void TestValuesExtremes() { var prev = UserInput.GetInterface(); try { CliUserInputInterface.Load(); TestObject obj = new TestObject(); var sem = new SemaphoreSlim(0); bool exceptionHappened = false; try { UserInput.Request(obj, TimeSpan.Zero, true); // should return immediately. Assert.Fail("Timeout exception should have been thrown"); } catch (TimeoutException) { } var trd = TapThread.Start(() => { try { UserInput.Request(obj, TimeSpan.MaxValue, true); } catch (OperationCanceledException) { exceptionHappened = true; } sem.Release(); }); trd.Abort(); if (!sem.Wait(1000) || exceptionHappened == false) { Assert.Fail("Should have been canceled by thread"); } } finally { UserInput.SetInterface(prev); } }
public void HierarchyLocalWithThreads() { // This test verifies that ThreadHierarchyLocal work with different thread contexts. // tapThreadLocalObject.LocalValue = (int)5; // At child thread level it should be 5. TapThread.WithNewContext(() => Assert.AreEqual(5, (int)tapThreadLocalObject.LocalValue)); TapThread.WithNewContext(() => { tapThreadLocalObject.LocalValue = (int)6; // At child-child thread level it should be 6. TapThread.WithNewContext(() => Assert.AreEqual(6, (int)tapThreadLocalObject.LocalValue)); }); // At this level it should still be 5. TapThread.WithNewContext(() => Assert.AreEqual(5, (int)tapThreadLocalObject.LocalValue)); // for no parent it should not have been set. TapThread.WithNewContext(() => Assert.IsNull(tapThreadLocalObject.LocalValue), parent: null); }
public static TapProcessContainer StartFromArgs(string args, TimeSpan timeOutAfter) { Process proc = new Process(); var container = new TapProcessContainer { TapProcess = proc }; var file = Path.GetDirectoryName(typeof(PluginManager).Assembly.Location); var files = new[] { Path.Combine(file, "tap.exe"), Path.Combine(file, "tap"), Path.Combine(file, "tap.dll") }; global::OpenTap.Log.CreateSource("test").Debug($"location: {file}"); var program = files.First(File.Exists); if (program.Contains(".dll")) { program = "dotnet"; args = $"\"{file}/tap.dll\" " + args; } proc.StartInfo = new ProcessStartInfo(program, args) { UseShellExecute = true, RedirectStandardOutput = true, RedirectStandardInput = true, RedirectStandardError = true, CreateNoWindow = true, }; proc.StartInfo.UseShellExecute = false; container.go(); TapThread.Start(() => { TapThread.Sleep(timeOutAfter); proc.Kill(); }); return(container); }
public void TimeoutOperationTest() { var sem = new System.Threading.Semaphore(0, 1); TapThread.Start(() => sem.Release()); sem.WaitOne(); { bool calledAction = false; var operation = TimeoutOperation.Create(TimeSpan.FromMilliseconds(1), () => { calledAction = true; sem.Release(); }); sem.WaitOne(); Assert.IsTrue(calledAction); } { bool calledAction = false; var operation = TimeoutOperation.Create(TimeSpan.FromMilliseconds(50), () => calledAction = true); operation.Dispose(); Assert.IsFalse(calledAction); } { bool calledAction = false; var operation = TimeoutOperation.Create(TimeSpan.FromMilliseconds(1), () => { calledAction = true; }); operation.Dispose(); Assert.IsFalse(calledAction); } { bool calledAction = false; var operation = TimeoutOperation.Create(TimeSpan.FromMilliseconds(1), () => calledAction = true); Assert.IsFalse(calledAction); operation.Dispose(); } }
public override void Run() { Sem.Release(); TapThread.Sleep(TimeSpan.MaxValue); }
public override void Run() { TapThread.Sleep(DelayMs); }
public void SweepRaceBug() { // test that validation rules can be checked while the test plan is running // without causing an error. The validation rules does not need to do actual validation // but since SweepLoop and SweepLoopRange modifies its child steps this could cause an error // as shown by SweepRaceBugCheckStep and SweepRaceBugStep. var plan = new TestPlan(); var repeat = new RepeatStep { Count = 10, Action = RepeatStep.RepeatStepAction.Fixed_Count }; var loop = new SweepLoop(); repeat.ChildTestSteps.Add(loop); loop.ChildTestSteps.Add(new SweepRaceBugStep() { }); loop.ChildTestSteps.Add(new SweepRaceBugCheckStep() { }); var steptype = TypeData.FromType(typeof(SweepRaceBugStep)); var member = steptype.GetMember(nameof(SweepRaceBugStep.Frequency)); var member2 = TypeData.FromType(typeof(SweepRaceBugCheckStep)).GetMember(nameof(SweepRaceBugCheckStep.Frequency2)); var lst = new List <SweepParam>(); double[] values = new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; lst.Add(new SweepParam(new[] { member }, values.Cast <object>().ToArray())); lst.Add(new SweepParam(new[] { member2 }, values.Cast <object>().ToArray())); loop.SweepParameters = lst; var loopRange = new SweepLoopRange(); loopRange.SweepStart = 1; loopRange.SweepEnd = 10; loopRange.SweepPoints = 10; loopRange.ChildTestSteps.Add(new SweepRaceBugStep() { }); loopRange.ChildTestSteps.Add(new SweepRaceBugCheckStep() { }); loopRange.SweepProperties = new List <IMemberData> { member, member2 }; var repeat2 = new RepeatStep { Count = 10, Action = RepeatStep.RepeatStepAction.Fixed_Count }; repeat2.ChildTestSteps.Add(loopRange); var parallel = new ParallelStep(); plan.ChildTestSteps.Add(parallel); parallel.ChildTestSteps.Add(repeat); parallel.ChildTestSteps.Add(repeat2); TestPlanRun run = null; TapThread.Start(() => run = plan.Execute()); TapThread.Start(() => { while (run == null) { loopRange.Error.ToList(); } }); while (run == null) { loop.Error.ToList(); } Assert.AreEqual(Verdict.NotSet, run.Verdict); }
public override void Run() { Int32 timeout = Timeout <= 0 ? Int32.MaxValue : Timeout; prepend = string.IsNullOrEmpty(LogHeader) ? "" : LogHeader + " "; var process = new Process(); process.StartInfo.FileName = Application; process.StartInfo.Arguments = Arguments; process.StartInfo.WorkingDirectory = Path.GetFullPath(WorkingDirectory); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; var abortRegistration = TapThread.Current.AbortToken.Register(() => { Log.Debug("Ending process '{0}'.", Application); process.Kill(); }); if (WaitForEnd) { output = new StringBuilder(); using (outputWaitHandle = new ManualResetEvent(false)) using (errorWaitHandle = new ManualResetEvent(false)) using (process) using (abortRegistration) { process.OutputDataReceived += OutputDataRecv; process.ErrorDataReceived += ErrorDataRecv; Log.Debug("Starting process {0} with arguments \"{1}\"", Application, Arguments); process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); var newlineArray = new [] { Environment.NewLine }; if (process.WaitForExit(timeout) && outputWaitHandle.WaitOne(timeout) && errorWaitHandle.WaitOne(timeout)) { var resultData = output.ToString(); ProcessOutput(resultData); if (CheckExitCode) { if (process.ExitCode != 0) { UpgradeVerdict(Verdict.Fail); } else { UpgradeVerdict(Verdict.Pass); } } } else { process.OutputDataReceived -= OutputDataRecv; process.ErrorDataReceived -= ErrorDataRecv; var resultData = output.ToString(); if (AddToLog) { foreach (var line in resultData.Split(newlineArray, StringSplitOptions.None)) { Log.Info("{0}{1}", prepend, line); } } ProcessOutput(resultData); Log.Error("Timed out while waiting for application. Trying to kill process..."); process.Kill(); UpgradeVerdict(Verdict.Fail); } } } else { TapThread.Start(() => { using (process) using (abortRegistration) { process.Start(); process.WaitForExit(); abortRegistration.Dispose(); } }); } }
public override void PostPlanRun() { // Delay was added so that behavior could be observed in the Timing Analyzer. TapThread.Sleep(100); base.PostPlanRun(); }
public override void Run() { TapThread.Sleep(TimeSpan.FromSeconds(DelaySec)); }
public override void Run() { TapThread.Sleep(Convert.ToInt32(Delay * 1000)); }
internal static List <string> DownloadPackages(string destinationDir, List <PackageDef> PackagesToDownload, List <string> filenames = null) { List <string> downloadedPackages = new List <string>(); for (int i = 0; i < PackagesToDownload.Count; i++) { Stopwatch timer = Stopwatch.StartNew(); var pkg = PackagesToDownload[i]; string filename = filenames?.ElementAtOrDefault(i) ?? Path.Combine(destinationDir, GetQualifiedFileName(pkg)); TapThread.ThrowIfAborted(); try { PackageDef existingPkg = null; try { // If the package we are installing is from a file, we should always use that file instead of a cached package. // During development a package might not change version but still have different content. if (pkg.PackageSource is FilePackageDefSource == false && File.Exists(filename)) { existingPkg = PackageDef.FromPackage(filename); } } catch (Exception e) { log.Warning("Could not open OpenTAP Package. Redownloading package.", e); File.Delete(filename); } if (existingPkg != null) { if (existingPkg.Version == pkg.Version && existingPkg.OS == pkg.OS && existingPkg.Architecture == pkg.Architecture) { if (!PackageCacheHelper.PackageIsFromCache(existingPkg)) { log.Info("Package '{0}' already exists in '{1}'.", pkg.Name, destinationDir); } else { log.Info("Package '{0}' already exists in cache '{1}'.", pkg.Name, destinationDir); } } else { throw new Exception($"A package already exists but it is not the same as the package that is being downloaded."); } } else { string source = (pkg.PackageSource as IRepositoryPackageDefSource)?.RepositoryUrl; if (source == null && pkg.PackageSource is FilePackageDefSource fileSource) { source = fileSource.PackageFilePath; } IPackageRepository rm = PackageRepositoryHelpers.DetermineRepositoryType(source); if (PackageCacheHelper.PackageIsFromCache(pkg)) { rm.DownloadPackage(pkg, filename); log.Info(timer, "Found package '{0}' in cache. Copied to '{1}'.", pkg.Name, Path.GetFullPath(filename)); } else { log.Debug("Downloading '{0}' version '{1}' from '{2}'", pkg.Name, pkg.Version, source); rm.DownloadPackage(pkg, filename); log.Info(timer, "Downloaded '{0}' to '{1}'.", pkg.Name, Path.GetFullPath(filename)); PackageCacheHelper.CachePackage(filename); } } } catch (Exception ex) { log.Error("Failed to download OpenTAP package."); throw ex; } downloadedPackages.Add(filename); } return(downloadedPackages); }
void IUserInputInterface.RequestUserInput(object dataObject, TimeSpan Timeout, bool modal) { if (readerThread == null) { lock (readerLock) { if (readerThread == null) { readerThread = TapThread.Start(() => { while (true) { lines.Add(Console.ReadLine()); } }, "Console Reader"); } } } DateTime TimeoutTime; if (Timeout == TimeSpan.MaxValue) { TimeoutTime = DateTime.MaxValue; } else { TimeoutTime = DateTime.Now + Timeout; } if (Timeout >= new TimeSpan(0, 0, 0, 0, int.MaxValue)) { Timeout = new TimeSpan(0, 0, 0, 0, -1); } do { if (platforDialogMutex.WaitOne(Timeout)) { break; } if (DateTime.Now >= TimeoutTime) { throw new TimeoutException("Request User Input timed out"); } } while (true); try { Log.Flush(); var a = AnnotationCollection.Annotate(dataObject); var mems = a.Get <IMembersAnnotation>()?.Members; if (mems == null) { return; } mems = mems.Concat(a.Get <IForwardedAnnotations>()?.Forwarded ?? Array.Empty <AnnotationCollection>()); var title = TypeData.GetTypeData(dataObject)?.GetMember("Name")?.GetValue(dataObject) as string; if (string.IsNullOrWhiteSpace(title) == false) { Console.WriteLine(title); } bool isBrowsable(IMemberData m) { var browsable = m.GetAttribute <System.ComponentModel.BrowsableAttribute>(); // Browsable overrides everything if (browsable != null) { return(browsable.Browsable); } if (m is IMemberData mem) { if (m.HasAttribute <OutputAttribute>()) { return(true); } if (!mem.Writable || !mem.Readable) { return(false); } return(true); } return(false); } foreach (var _message in mems) { var mem = _message.Get <IMemberAnnotation>()?.Member; if (mem != null) { if (!isBrowsable(mem)) { continue; } } log.Flush(); var str = _message.Get <IStringValueAnnotation>(); if (str == null) { continue; } var name = _message.Get <DisplayAttribute>()?.Name; start: var isVisible = _message.Get <IAccessAnnotation>()?.IsVisible ?? true; if (!isVisible) { continue; } var isReadOnly = _message.Get <IAccessAnnotation>()?.IsReadOnly ?? false; if (isReadOnly) { Console.WriteLine($"{str.Value}"); continue; } var proxy = _message.Get <IAvailableValuesAnnotationProxy>(); List <string> options = null; bool pleaseEnter = true; if (proxy != null) { pleaseEnter = false; options = new List <string>(); int index = 0; var current_value = proxy.SelectedValue; foreach (var value in proxy.AvailableValues) { var v = value.Get <IStringValueAnnotation>(); if (v != null) { Console.Write("{1}: '{0}'", v.Value, index); if (value == current_value) { Console.WriteLine(" (default)"); } else { Console.WriteLine(); } } options.Add(v?.Value); index++; } Console.Write("Please enter a number or name "); } var layout = _message.Get <IMemberAnnotation>()?.Member.GetAttribute <LayoutAttribute>(); bool showName = layout?.Mode.HasFlag(LayoutMode.FullRow) == true ? false : true; if (pleaseEnter) { Console.Write("Please enter "); } if (showName) { Console.Write($"{name} ({str.Value}): "); } else { Console.Write($"({str.Value}): "); } var read = (awaitReadLine(TimeoutTime) ?? "").Trim(); if (read == "") { // accept the default value. continue; } try { if (options != null && int.TryParse(read, out int result)) { if (result < options.Count) { read = options[result]; } else { goto start; } } str.Value = read; var err = a.Get <IErrorAnnotation>(); IEnumerable <string> errors = err?.Errors; _message.Write(); if (errors?.Any() == true) { Console.WriteLine("Unable to parse value {0}", read); goto start; } } catch (Exception) { Console.WriteLine("Unable to parse '{0}'", read); goto start; } } a.Write(); } finally { platforDialogMutex.ReleaseMutex(); } }