ExampleRunner.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. using System.Diagnostics;
  2. using System.Diagnostics.CodeAnalysis;
  3. using System.Reflection;
  4. using System.Text.Json;
  5. using System.Text.RegularExpressions;
  6. namespace Terminal.Gui.Examples;
  7. /// <summary>
  8. /// Provides methods for running example applications in various execution modes.
  9. /// </summary>
  10. public static class ExampleRunner
  11. {
  12. /// <summary>
  13. /// Runs an example with the specified context.
  14. /// </summary>
  15. /// <param name="example">The example information.</param>
  16. /// <param name="context">The execution context.</param>
  17. /// <returns>The result of running the example.</returns>
  18. [RequiresUnreferencedCode ("Calls System.Reflection.Assembly.LoadFrom")]
  19. [RequiresDynamicCode ("Calls System.Reflection.Assembly.LoadFrom")]
  20. public static ExampleResult Run (ExampleInfo example, ExampleContext context)
  21. {
  22. return context.Mode == ExecutionMode.InProcess
  23. ? RunInProcess (example, context)
  24. : RunOutOfProcess (example, context);
  25. }
  26. [RequiresUnreferencedCode ("Calls System.Reflection.Assembly.LoadFrom")]
  27. [RequiresDynamicCode ("Calls System.Reflection.Assembly.LoadFrom")]
  28. private static ExampleResult RunInProcess (ExampleInfo example, ExampleContext context)
  29. {
  30. Environment.SetEnvironmentVariable (
  31. ExampleContext.EnvironmentVariableName,
  32. context.ToJson ());
  33. try
  34. {
  35. Assembly asm = Assembly.LoadFrom (example.AssemblyPath);
  36. MethodInfo? entryPoint = asm.EntryPoint;
  37. if (entryPoint is null)
  38. {
  39. return new ()
  40. {
  41. Success = false,
  42. ErrorMessage = "Assembly does not have an entry point"
  43. };
  44. }
  45. ParameterInfo [] parameters = entryPoint.GetParameters ();
  46. object? result = null;
  47. if (parameters.Length == 0)
  48. {
  49. result = entryPoint.Invoke (null, null);
  50. }
  51. else if (parameters.Length == 1 && parameters [0].ParameterType == typeof (string []))
  52. {
  53. result = entryPoint.Invoke (null, new object [] { Array.Empty<string> () });
  54. }
  55. else
  56. {
  57. return new ()
  58. {
  59. Success = false,
  60. ErrorMessage = "Entry point has unsupported signature"
  61. };
  62. }
  63. // If entry point returns Task, wait for it
  64. if (result is Task task)
  65. {
  66. task.GetAwaiter ().GetResult ();
  67. }
  68. return new ()
  69. {
  70. Success = true
  71. };
  72. }
  73. catch (Exception ex)
  74. {
  75. return new ()
  76. {
  77. Success = false,
  78. ErrorMessage = ex.ToString ()
  79. };
  80. }
  81. finally
  82. {
  83. Environment.SetEnvironmentVariable (ExampleContext.EnvironmentVariableName, null);
  84. }
  85. }
  86. private static ExampleResult RunOutOfProcess (ExampleInfo example, ExampleContext context)
  87. {
  88. ProcessStartInfo psi = new ()
  89. {
  90. FileName = "dotnet",
  91. Arguments = $"\"{example.AssemblyPath}\"",
  92. UseShellExecute = false,
  93. RedirectStandardOutput = true,
  94. RedirectStandardError = true,
  95. CreateNoWindow = true
  96. };
  97. psi.Environment [ExampleContext.EnvironmentVariableName] = context.ToJson ();
  98. using Process? process = Process.Start (psi);
  99. if (process is null)
  100. {
  101. return new ()
  102. {
  103. Success = false,
  104. ErrorMessage = "Failed to start process"
  105. };
  106. }
  107. bool exited = process.WaitForExit (context.TimeoutMs);
  108. string stdout = process.StandardOutput.ReadToEnd ();
  109. string stderr = process.StandardError.ReadToEnd ();
  110. if (!exited)
  111. {
  112. try
  113. {
  114. const bool killEntireProcessTree = true;
  115. process.Kill (killEntireProcessTree);
  116. }
  117. catch
  118. {
  119. // Ignore errors killing the process
  120. }
  121. return new ()
  122. {
  123. Success = false,
  124. TimedOut = true,
  125. StandardOutput = stdout,
  126. StandardError = stderr
  127. };
  128. }
  129. ExampleMetrics? metrics = ExtractMetricsFromOutput (stdout);
  130. return new ()
  131. {
  132. Success = process.ExitCode == 0,
  133. ExitCode = process.ExitCode,
  134. StandardOutput = stdout,
  135. StandardError = stderr,
  136. Metrics = metrics
  137. };
  138. }
  139. private static ExampleMetrics? ExtractMetricsFromOutput (string output)
  140. {
  141. // Look for the metrics marker in the output
  142. Match match = Regex.Match (output, @"###TERMGUI_METRICS:(.+?)###");
  143. if (!match.Success)
  144. {
  145. return null;
  146. }
  147. try
  148. {
  149. return JsonSerializer.Deserialize<ExampleMetrics> (match.Groups [1].Value);
  150. }
  151. catch
  152. {
  153. return null;
  154. }
  155. }
  156. }