using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; using System.Text.RegularExpressions; namespace Terminal.Gui.Examples; /// /// Provides methods for running example applications in various execution modes. /// public static class ExampleRunner { /// /// Runs an example with the specified context. /// /// The example information. /// The execution context. /// The result of running the example. [RequiresUnreferencedCode ("Calls System.Reflection.Assembly.LoadFrom")] [RequiresDynamicCode ("Calls System.Reflection.Assembly.LoadFrom")] public static ExampleResult Run (ExampleInfo example, ExampleContext context) { return context.Mode == ExecutionMode.InProcess ? RunInProcess (example, context) : RunOutOfProcess (example, context); } private static ExampleMetrics? ExtractMetricsFromOutput (string output) { // Look for the metrics marker in the output Match match = Regex.Match (output, @"###TERMGUI_METRICS:(.+?)###"); if (!match.Success) { return null; } try { return JsonSerializer.Deserialize (match.Groups [1].Value); } catch { return null; } } [RequiresUnreferencedCode ("Calls System.Reflection.Assembly.LoadFrom")] [RequiresDynamicCode ("Calls System.Reflection.Assembly.LoadFrom")] private static ExampleResult RunInProcess (ExampleInfo example, ExampleContext context) { Environment.SetEnvironmentVariable ( ExampleContext.ENVIRONMENT_VARIABLE_NAME, context.ToJson ()); try { Assembly asm = Assembly.LoadFrom (example.AssemblyPath); MethodInfo? entryPoint = asm.EntryPoint; if (entryPoint is null) { return new () { Success = false, ErrorMessage = "Assembly does not have an entry point" }; } ParameterInfo [] parameters = entryPoint.GetParameters (); Task executionTask = Task.Run (() => { object? result = null; if (parameters.Length == 0) { result = entryPoint.Invoke (null, null); } else if (parameters.Length == 1 && parameters [0].ParameterType == typeof (string [])) { result = entryPoint.Invoke (null, [Array.Empty ()]); } else { throw new InvalidOperationException ("Entry point has unsupported signature"); } // If entry point returns Task, wait for it if (result is Task task) { task.GetAwaiter ().GetResult (); } }); bool completed = executionTask.Wait (context.TimeoutMs); if (!completed) { // reset terminal Console.Clear (); return new () { Success = false, TimedOut = true }; } if (executionTask.Exception is { }) { throw executionTask.Exception.GetBaseException (); } return new () { Success = true }; } catch (Exception ex) { return new () { Success = false, ErrorMessage = ex.ToString () }; } finally { Environment.SetEnvironmentVariable (ExampleContext.ENVIRONMENT_VARIABLE_NAME, null); } } private static ExampleResult RunOutOfProcess (ExampleInfo example, ExampleContext context) { ProcessStartInfo psi = new () { FileName = "dotnet", Arguments = $"\"{example.AssemblyPath}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true }; psi.Environment [ExampleContext.ENVIRONMENT_VARIABLE_NAME] = context.ToJson (); using Process? process = Process.Start (psi); if (process is null) { return new () { Success = false, ErrorMessage = "Failed to start process" }; } bool exited = process.WaitForExit (context.TimeoutMs); string stdout = process.StandardOutput.ReadToEnd (); string stderr = process.StandardError.ReadToEnd (); if (!exited) { try { const bool KILL_ENTIRE_PROCESS_TREE = true; process.Kill (KILL_ENTIRE_PROCESS_TREE); } catch { // Ignore errors killing the process } return new () { Success = false, TimedOut = true, StandardOutput = stdout, StandardError = stderr }; } ExampleMetrics? metrics = ExtractMetricsFromOutput (stdout); return new () { Success = process.ExitCode == 0, ExitCode = process.ExitCode, StandardOutput = stdout, StandardError = stderr, Metrics = metrics }; } }