ExampleRunner.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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.Wait ();
  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. process.Kill (true);
  115. }
  116. catch
  117. {
  118. // Ignore errors killing the process
  119. }
  120. return new ()
  121. {
  122. Success = false,
  123. TimedOut = true,
  124. StandardOutput = stdout,
  125. StandardError = stderr
  126. };
  127. }
  128. ExampleMetrics? metrics = ExtractMetricsFromOutput (stdout);
  129. return new ()
  130. {
  131. Success = process.ExitCode == 0,
  132. ExitCode = process.ExitCode,
  133. StandardOutput = stdout,
  134. StandardError = stderr,
  135. Metrics = metrics
  136. };
  137. }
  138. private static ExampleMetrics? ExtractMetricsFromOutput (string output)
  139. {
  140. // Look for the metrics marker in the output
  141. Match match = Regex.Match (output, @"###TERMGUI_METRICS:(.+?)###");
  142. if (!match.Success)
  143. {
  144. return null;
  145. }
  146. try
  147. {
  148. return JsonSerializer.Deserialize<ExampleMetrics> (match.Groups [1].Value);
  149. }
  150. catch
  151. {
  152. return null;
  153. }
  154. }
  155. }