microsoft/TypeAgent

Public

mirrored fromhttps://github.com/microsoft/TypeAgentAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
d4944c6517c9a96a3c419f827769f518913f1b75

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

dotnet/autoShell.Tests/AutoShellProcess.cs

150lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4using System.Diagnostics;
5
6namespace autoShell.Tests;
7
8/// <summary>
9/// Manages an autoShell.exe child process with redirected stdin/stdout
10/// for end-to-end testing of the JSON command protocol.
11/// </summary>
12internal sealed class AutoShellProcess : IDisposable
13{
14 private static readonly string s_exePath = Path.Combine(AppContext.BaseDirectory, "autoShell.exe");
15
16 private readonly Process _process;
17
18 private AutoShellProcess(Process process)
19 {
20 _process = process;
21 }
22
23 /// <summary>
24 /// Starts autoShell.exe in interactive (stdin) mode with redirected I/O.
25 /// </summary>
26 public static AutoShellProcess StartInteractive()
27 {
28 var psi = new ProcessStartInfo
29 {
30 FileName = s_exePath,
31 RedirectStandardInput = true,
32 RedirectStandardOutput = true,
33 RedirectStandardError = true,
34 UseShellExecute = false,
35 CreateNoWindow = true,
36 };
37
38 var process = Process.Start(psi)
39 ?? throw new InvalidOperationException("Failed to start autoShell.exe");
40
41 return new AutoShellProcess(process);
42 }
43
44 /// <summary>
45 /// Starts autoShell.exe with command-line arguments (non-interactive mode).
46 /// Returns stdout content and exit code after the process completes.
47 /// </summary>
48 public static (string Output, int ExitCode) RunWithArgs(string args, int timeoutMs = 10000)
49 {
50 var psi = new ProcessStartInfo
51 {
52 FileName = s_exePath,
53 Arguments = args,
54 RedirectStandardOutput = true,
55 RedirectStandardError = true,
56 UseShellExecute = false,
57 CreateNoWindow = true,
58 };
59
60 using var process = Process.Start(psi)!;
61 string output = process.StandardOutput.ReadToEnd();
62 bool exited = process.WaitForExit(timeoutMs);
63
64 if (!exited)
65 {
66 process.Kill();
67 throw new TimeoutException($"autoShell.exe did not exit within {timeoutMs}ms");
68 }
69
70 return (output, process.ExitCode);
71 }
72
73 /// <summary>
74 /// Sends a JSON command string to stdin, terminated with \r\n.
75 /// </summary>
76 public void SendCommand(string json)
77 {
78 _process.StandardInput.WriteLine(json);
79 _process.StandardInput.Flush();
80 }
81
82 /// <summary>
83 /// Reads a single line of stdout with a timeout.
84 /// Returns null if the timeout expires before a line is available.
85 /// Uses a lock to prevent concurrent reads on the same stream.
86 /// </summary>
87 private readonly SemaphoreSlim _readLock = new(1, 1);
88
89 public async Task<string?> ReadLineAsync(int timeoutMs = 5000)
90 {
91 await _readLock.WaitAsync();
92 try
93 {
94 using var cts = new CancellationTokenSource(timeoutMs);
95 return await _process.StandardOutput.ReadLineAsync(cts.Token).AsTask();
96 }
97 catch (OperationCanceledException)
98 {
99 return null;
100 }
101 finally
102 {
103 _readLock.Release();
104 }
105 }
106
107 /// <summary>
108 /// Sends a quit command and waits for the process to exit.
109 /// </summary>
110 public void SendQuit(int timeoutMs = 5000)
111 {
112 SendCommand("""{"actionName":"quit","parameters":{}}""");
113 _process.WaitForExit(timeoutMs);
114 }
115
116 /// <summary>
117 /// Returns true if the process has exited.
118 /// </summary>
119 public bool HasExited => _process.HasExited;
120
121 /// <summary>
122 /// Closes the stdin stream (sends EOF).
123 /// </summary>
124 public void CloseStdin()
125 {
126 _process.StandardInput.Close();
127 }
128
129 /// <summary>
130 /// Waits for the process to exit within the given timeout.
131 /// </summary>
132 public bool WaitForExit(int timeoutMs)
133 {
134 return _process.WaitForExit(timeoutMs);
135 }
136
137 public void Dispose()
138 {
139 try
140 {
141 if (!_process.HasExited)
142 {
143 _process.Kill();
144 _process.WaitForExit(3000);
145 }
146 }
147 catch { }
148 _process.Dispose();
149 }
150}
151