Skip to content

Commit ca5cd91

Browse files
Replace RunTests scripts with .NET app (#20337)
1 parent b73e221 commit ca5cd91

File tree

10 files changed

+444
-173
lines changed

10 files changed

+444
-173
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<Project>
2+
</Project>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<Project>
2+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace RunTests
6+
{
7+
public class ProcessResult
8+
{
9+
public ProcessResult(string standardOutput, string standardError, int exitCode)
10+
{
11+
StandardOutput = standardOutput;
12+
StandardError = standardError;
13+
ExitCode = exitCode;
14+
}
15+
16+
public string StandardOutput { get; }
17+
public string StandardError { get; }
18+
public int ExitCode { get; }
19+
}
20+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Diagnostics;
8+
using System.Runtime.InteropServices;
9+
using System.Text;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
#nullable enable
14+
15+
namespace RunTests
16+
{
17+
public static class ProcessUtil
18+
{
19+
[DllImport("libc", SetLastError = true, EntryPoint = "kill")]
20+
private static extern int sys_kill(int pid, int sig);
21+
22+
public static async Task<ProcessResult> RunAsync(
23+
string filename,
24+
string arguments,
25+
string? workingDirectory = null,
26+
bool throwOnError = true,
27+
IDictionary<string, string?>? environmentVariables = null,
28+
Action<string>? outputDataReceived = null,
29+
Action<string>? errorDataReceived = null,
30+
Action<int>? onStart = null,
31+
CancellationToken cancellationToken = default)
32+
{
33+
Console.WriteLine($"Running '{filename} {arguments}'");
34+
using var process = new Process()
35+
{
36+
StartInfo =
37+
{
38+
FileName = filename,
39+
Arguments = arguments,
40+
RedirectStandardOutput = true,
41+
RedirectStandardError = true,
42+
UseShellExecute = false,
43+
CreateNoWindow = true,
44+
},
45+
EnableRaisingEvents = true
46+
};
47+
48+
49+
if (workingDirectory != null)
50+
{
51+
process.StartInfo.WorkingDirectory = workingDirectory;
52+
}
53+
54+
if (environmentVariables != null)
55+
{
56+
foreach (var kvp in environmentVariables)
57+
{
58+
process.StartInfo.Environment.Add(kvp);
59+
}
60+
}
61+
62+
var outputBuilder = new StringBuilder();
63+
process.OutputDataReceived += (_, e) =>
64+
{
65+
if (e.Data != null)
66+
{
67+
if (outputDataReceived != null)
68+
{
69+
outputDataReceived.Invoke(e.Data);
70+
}
71+
else
72+
{
73+
outputBuilder.AppendLine(e.Data);
74+
}
75+
}
76+
};
77+
78+
var errorBuilder = new StringBuilder();
79+
process.ErrorDataReceived += (_, e) =>
80+
{
81+
if (e.Data != null)
82+
{
83+
if (errorDataReceived != null)
84+
{
85+
errorDataReceived.Invoke(e.Data);
86+
}
87+
else
88+
{
89+
errorBuilder.AppendLine(e.Data);
90+
}
91+
}
92+
};
93+
94+
var processLifetimeTask = new TaskCompletionSource<ProcessResult>();
95+
96+
process.Exited += (_, e) =>
97+
{
98+
Console.WriteLine($"'{process.StartInfo.FileName} {process.StartInfo.Arguments}' completed with exit code '{process.ExitCode}'");
99+
if (throwOnError && process.ExitCode != 0)
100+
{
101+
processLifetimeTask.TrySetException(new InvalidOperationException($"Command {filename} {arguments} returned exit code {process.ExitCode}"));
102+
}
103+
else
104+
{
105+
processLifetimeTask.TrySetResult(new ProcessResult(outputBuilder.ToString(), errorBuilder.ToString(), process.ExitCode));
106+
}
107+
};
108+
109+
process.Start();
110+
onStart?.Invoke(process.Id);
111+
112+
process.BeginOutputReadLine();
113+
process.BeginErrorReadLine();
114+
115+
var cancelledTcs = new TaskCompletionSource<object?>();
116+
await using var _ = cancellationToken.Register(() => cancelledTcs.TrySetResult(null));
117+
118+
var result = await Task.WhenAny(processLifetimeTask.Task, cancelledTcs.Task);
119+
120+
if (result == cancelledTcs.Task)
121+
{
122+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
123+
{
124+
sys_kill(process.Id, sig: 2); // SIGINT
125+
126+
var cancel = new CancellationTokenSource();
127+
128+
await Task.WhenAny(processLifetimeTask.Task, Task.Delay(TimeSpan.FromSeconds(5), cancel.Token));
129+
130+
cancel.Cancel();
131+
}
132+
133+
if (!process.HasExited)
134+
{
135+
process.CloseMainWindow();
136+
137+
if (!process.HasExited)
138+
{
139+
process.Kill();
140+
}
141+
}
142+
}
143+
144+
return await processLifetimeTask.Task;
145+
}
146+
147+
public static void KillProcess(int pid)
148+
{
149+
try
150+
{
151+
using var process = Process.GetProcessById(pid);
152+
process?.Kill();
153+
}
154+
catch (ArgumentException) { }
155+
catch (InvalidOperationException) { }
156+
}
157+
}
158+
}

0 commit comments

Comments
 (0)