Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 592d2c4

Browse files
committed
Update Process tests to use RemoteExecutor
System.Diagnostics.Process has its own separate test console app that the tests assembly spawns with various arguments. This design requires having a separate assembly, and splitting logic across files, across assemblies, etc., making it error prone. It also make it difficult to enable more complicated scenarios, such as launching child processes from the child process. This commit moves System.Diagnostics.Process.Tests to use the centralize remote process launcher that lets all of the logic be defined together in the one tests assembly, including using lambdas.
1 parent 967286d commit 592d2c4

21 files changed

+222
-1191
lines changed

src/System.Diagnostics.Process/System.Diagnostics.Process.sln

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{EFFF9E
1010
..\.nuget\packages.Windows_NT.config = ..\.nuget\packages.Windows_NT.config
1111
EndProjectSection
1212
EndProject
13-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Diagnostics.Process.Tests", "tests\System.Diagnostics.Process.Tests\System.Diagnostics.Process.Tests.csproj", "{E1114510-844C-4BB2-BBAD-8595BD16E24B}"
13+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Diagnostics.Process.Tests", "tests\System.Diagnostics.Process.Tests.csproj", "{E1114510-844C-4BB2-BBAD-8595BD16E24B}"
1414
EndProject
15-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Diagnostics.Process.TestConsoleApp", "tests\System.Diagnostics.Process.TestConsoleApp\System.Diagnostics.Process.TestConsoleApp.csproj", "{69E46A6F-9966-45A5-8945-2559FE337827}"
15+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteExecutorConsoleApp", "..\Common\tests\System\Diagnostics\RemoteExecutorConsoleApp\RemoteExecutorConsoleApp.csproj", "{F5E941C8-AF2F-47AB-A066-FF25470CE382}"
1616
EndProject
1717
Global
1818
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -44,14 +44,14 @@ Global
4444
{E1114510-844C-4BB2-BBAD-8595BD16E24B}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU
4545
{E1114510-844C-4BB2-BBAD-8595BD16E24B}.Windows_Release|Any CPU.ActiveCfg = Release|Any CPU
4646
{E1114510-844C-4BB2-BBAD-8595BD16E24B}.Windows_Release|Any CPU.Build.0 = Release|Any CPU
47-
{69E46A6F-9966-45A5-8945-2559FE337827}.Linux_Debug|Any CPU.ActiveCfg = Debug|Any CPU
48-
{69E46A6F-9966-45A5-8945-2559FE337827}.Linux_Release|Any CPU.ActiveCfg = Release|Any CPU
49-
{69E46A6F-9966-45A5-8945-2559FE337827}.OSX_Debug|Any CPU.ActiveCfg = Debug|Any CPU
50-
{69E46A6F-9966-45A5-8945-2559FE337827}.OSX_Release|Any CPU.ActiveCfg = Release|Any CPU
51-
{69E46A6F-9966-45A5-8945-2559FE337827}.Windows_Debug|Any CPU.ActiveCfg = Debug|Any CPU
52-
{69E46A6F-9966-45A5-8945-2559FE337827}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU
53-
{69E46A6F-9966-45A5-8945-2559FE337827}.Windows_Release|Any CPU.ActiveCfg = Release|Any CPU
54-
{69E46A6F-9966-45A5-8945-2559FE337827}.Windows_Release|Any CPU.Build.0 = Release|Any CPU
47+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.Linux_Debug|Any CPU.ActiveCfg = Debug|Any CPU
48+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.Linux_Release|Any CPU.ActiveCfg = Release|Any CPU
49+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.OSX_Debug|Any CPU.ActiveCfg = Debug|Any CPU
50+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.OSX_Release|Any CPU.ActiveCfg = Release|Any CPU
51+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.Windows_Debug|Any CPU.ActiveCfg = Debug|Any CPU
52+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.Windows_Debug|Any CPU.Build.0 = Debug|Any CPU
53+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.Windows_Release|Any CPU.ActiveCfg = Release|Any CPU
54+
{F5E941C8-AF2F-47AB-A066-FF25470CE382}.Windows_Release|Any CPU.Build.0 = Release|Any CPU
5555
EndGlobalSection
5656
GlobalSection(SolutionProperties) = preSolution
5757
HideSolutionNode = FALSE

src/System.Diagnostics.Process/src/System/Diagnostics/ProcessWaitState.Unix.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,9 @@ private void CheckForExit(bool blockingAllowed = false)
330330
int killResult = Interop.libc.kill(_processId, 0); // 0 means don't send a signal
331331
if (killResult == 0)
332332
{
333-
// Process is still running
333+
// Process is still running. This could also be a defunct process that has completed
334+
// its work but still has an entry in the processes table due to its parent not yet
335+
// having waited on it to clean it up.
334336
return;
335337
}
336338
else // error from kill
Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -4,44 +4,20 @@
44
using System.Text;
55
using System.IO;
66
using Xunit;
7+
using System.Threading;
78

89
namespace System.Diagnostics.ProcessTests
910
{
1011
public class ProcessStreamReadTests : ProcessTestBase
1112
{
12-
private Process CreateProcessError()
13-
{
14-
return CreateProcess("error");
15-
}
16-
17-
private Process CreateProcessInput()
18-
{
19-
return CreateProcess("input");
20-
}
21-
22-
private Process CreateProcessStream()
23-
{
24-
return CreateProcess("stream");
25-
}
26-
27-
private Process CreateProcessByteAtATime()
28-
{
29-
return CreateProcess("byteAtATime");
30-
}
31-
32-
private Process CreateProcessManyOutputLines()
33-
{
34-
return CreateProcess("manyOutputLines");
35-
}
36-
3713
[Fact]
3814
public void TestSyncErrorStream()
3915
{
40-
Process p = CreateProcessError();
16+
Process p = CreateProcess(ErrorProcessBody);
4117
p.StartInfo.RedirectStandardError = true;
4218
p.Start();
43-
string expected = TestExeName + " started error stream" + Environment.NewLine +
44-
TestExeName + " closed error stream" + Environment.NewLine;
19+
string expected = TestConsoleApp + " started error stream" + Environment.NewLine +
20+
TestConsoleApp + " closed error stream" + Environment.NewLine;
4521
Assert.Equal(expected, p.StandardError.ReadToEnd());
4622
Assert.True(p.WaitForExit(WaitInMS));
4723
}
@@ -52,7 +28,7 @@ public void TestAsyncErrorStream()
5228
for (int i = 0; i < 2; ++i)
5329
{
5430
StringBuilder sb = new StringBuilder();
55-
Process p = CreateProcessError();
31+
Process p = CreateProcess(ErrorProcessBody);
5632
p.StartInfo.RedirectStandardError = true;
5733
p.ErrorDataReceived += (s, e) =>
5834
{
@@ -68,20 +44,28 @@ public void TestAsyncErrorStream()
6844
Assert.True(p.WaitForExit(WaitInMS));
6945
p.WaitForExit(); // This ensures async event handlers are finished processing.
7046

71-
string expected = TestExeName + " started error stream" + (i == 1 ? "" : TestExeName + " closed error stream");
47+
string expected = TestConsoleApp + " started error stream" + (i == 1 ? "" : TestConsoleApp + " closed error stream");
7248
Assert.Equal(expected, sb.ToString());
7349
}
7450
}
7551

52+
private static int ErrorProcessBody()
53+
{
54+
Console.Error.WriteLine(TestConsoleApp + " started error stream");
55+
Console.Error.WriteLine(TestConsoleApp + " closed error stream");
56+
return SuccessExitCode;
57+
}
58+
59+
7660
[Fact]
7761
public void TestSyncOutputStream()
7862
{
79-
Process p = CreateProcessStream();
63+
Process p = CreateProcess(StreamBody);
8064
p.StartInfo.RedirectStandardOutput = true;
8165
p.Start();
8266
string s = p.StandardOutput.ReadToEnd();
8367
Assert.True(p.WaitForExit(WaitInMS));
84-
Assert.Equal(TestExeName + " started" + Environment.NewLine + TestExeName + " closed" + Environment.NewLine, s);
68+
Assert.Equal(TestConsoleApp + " started" + Environment.NewLine + TestConsoleApp + " closed" + Environment.NewLine, s);
8569
}
8670

8771
[Fact]
@@ -90,7 +74,7 @@ public void TestAsyncOutputStream()
9074
for (int i = 0; i < 2; ++i)
9175
{
9276
StringBuilder sb = new StringBuilder();
93-
Process p = CreateProcessStream();
77+
Process p = CreateProcess(StreamBody);
9478
p.StartInfo.RedirectStandardOutput = true;
9579
p.OutputDataReceived += (s, e) =>
9680
{
@@ -105,16 +89,27 @@ public void TestAsyncOutputStream()
10589
Assert.True(p.WaitForExit(WaitInMS));
10690
p.WaitForExit(); // This ensures async event handlers are finished processing.
10791

108-
string expected = TestExeName + " started" + (i == 1 ? "" : TestExeName + " closed");
92+
string expected = TestConsoleApp + " started" + (i == 1 ? "" : TestConsoleApp + " closed");
10993
Assert.Equal(expected, sb.ToString());
11094
}
11195
}
11296

97+
private static int StreamBody()
98+
{
99+
Console.WriteLine(TestConsoleApp + " started");
100+
Console.WriteLine(TestConsoleApp + " closed");
101+
return SuccessExitCode;
102+
}
103+
113104
[Fact]
114105
public void TestSyncStreams()
115106
{
116107
const string expected = "This string should come as output";
117-
Process p = CreateProcessInput();
108+
Process p = CreateProcess(() =>
109+
{
110+
Console.ReadLine();
111+
return SuccessExitCode;
112+
});
118113
p.StartInfo.RedirectStandardInput = true;
119114
p.StartInfo.RedirectStandardOutput = true;
120115
p.OutputDataReceived += (s, e) => { Assert.Equal(expected, e.Data); };
@@ -130,7 +125,19 @@ public void TestSyncStreams()
130125
public void TestAsyncHalfCharacterAtATime()
131126
{
132127
var receivedOutput = false;
133-
Process p = CreateProcessByteAtATime();
128+
Process p = CreateProcess(() =>
129+
{
130+
var stdout = Console.OpenStandardOutput();
131+
var bytes = new byte[] { 97, 0 }; //Encoding.Unicode.GetBytes("a");
132+
133+
for (int i = 0; i != bytes.Length; ++i)
134+
{
135+
stdout.WriteByte(bytes[i]);
136+
stdout.Flush();
137+
Thread.Sleep(100);
138+
}
139+
return SuccessExitCode;
140+
});
134141
p.StartInfo.RedirectStandardOutput = true;
135142
p.StartInfo.StandardOutputEncoding = Encoding.Unicode;
136143
p.OutputDataReceived += (s, e) =>
@@ -153,10 +160,19 @@ public void TestAsyncHalfCharacterAtATime()
153160
[Fact]
154161
public void TestManyOutputLines()
155162
{
163+
const int ExpectedLineCount = 144;
164+
156165
int nonWhitespaceLinesReceived = 0;
157166
int totalLinesReceived = 0;
158167

159-
Process p = CreateProcessManyOutputLines();
168+
Process p = CreateProcess(() =>
169+
{
170+
for (int i = 0; i < ExpectedLineCount; i++)
171+
{
172+
Console.WriteLine("This is line #" + i + ".");
173+
}
174+
return SuccessExitCode;
175+
});
160176
p.StartInfo.RedirectStandardOutput = true;
161177
p.OutputDataReceived += (s, e) =>
162178
{
@@ -172,8 +188,8 @@ public void TestManyOutputLines()
172188
Assert.True(p.WaitForExit(WaitInMS));
173189
p.WaitForExit(); // This ensures async event handlers are finished processing.
174190

175-
Assert.Equal(144, nonWhitespaceLinesReceived);
176-
Assert.Equal(145, totalLinesReceived);
191+
Assert.Equal(ExpectedLineCount, nonWhitespaceLinesReceived);
192+
Assert.Equal(ExpectedLineCount + 1, totalLinesReceived);
177193
}
178194

179195
[Fact]
@@ -190,7 +206,7 @@ public void TestStreamNegativeTests()
190206
}
191207

192208
{
193-
Process p = CreateProcessStream();
209+
Process p = CreateProcess(StreamBody);
194210
p.StartInfo.RedirectStandardOutput = true;
195211
p.StartInfo.RedirectStandardError = true;
196212
p.OutputDataReceived += (s, e) => {};
@@ -206,7 +222,7 @@ public void TestStreamNegativeTests()
206222
}
207223

208224
{
209-
Process p = CreateProcessStream();
225+
Process p = CreateProcess(StreamBody);
210226
p.StartInfo.RedirectStandardOutput = true;
211227
p.StartInfo.RedirectStandardError = true;
212228
p.OutputDataReceived += (s, e) => {};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
7+
namespace System.Diagnostics.ProcessTests
8+
{
9+
public class ProcessTestBase : RemoteExecutorTestBase
10+
{
11+
protected const int WaitInMS = 100 * 1000;
12+
protected readonly Process _process;
13+
protected readonly List<Process> _processes = new List<Process>();
14+
15+
public ProcessTestBase()
16+
{
17+
_process = CreateProcessInfinite();
18+
_process.Start();
19+
}
20+
21+
protected override void Dispose(bool disposing)
22+
{
23+
// Wait for all started processes to complete
24+
foreach (Process p in _processes)
25+
{
26+
try
27+
{
28+
if (!p.HasExited)
29+
{
30+
p.Kill();
31+
Assert.True(p.WaitForExit(WaitInMS));
32+
}
33+
}
34+
catch (InvalidOperationException) { } // in case it was never started
35+
}
36+
}
37+
38+
protected Process CreateProcess(Func<int> method = null)
39+
{
40+
Process p = RemoteInvoke(method ?? (() => SuccessExitCode), start: false).Process;
41+
lock (_processes)
42+
{
43+
_processes.Add(p);
44+
}
45+
return p;
46+
}
47+
48+
protected Process CreateProcess(Func<string, int> method, string arg)
49+
{
50+
Process p = RemoteInvoke(method, arg, start: false).Process;
51+
lock (_processes)
52+
{
53+
_processes.Add(p);
54+
}
55+
return p;
56+
}
57+
58+
protected Process CreateProcessInfinite()
59+
{
60+
return CreateProcess(() =>
61+
{
62+
Thread.Sleep(WaitInMS);
63+
return SuccessExitCode;
64+
});
65+
}
66+
67+
protected void StartSleepKillWait(Process p)
68+
{
69+
p.Start();
70+
Thread.Sleep(50);
71+
p.Kill();
72+
Assert.True(p.WaitForExit(WaitInMS));
73+
}
74+
75+
}
76+
}

0 commit comments

Comments
 (0)