Update tests to reflect when entrypoint is available.
internal ProcessInfo GetProcessInfo()
{
- // RE: https://github.com/dotnet/runtime/issues/54083
- // If the GetProcessInfo2 command is sent too early, it will crash the runtime instance.
- // Disable the usage of the command until that issue is fixed.
-
// Attempt to get ProcessInfo v2
- //ProcessInfo processInfo = TryGetProcessInfo2();
- //if (null != processInfo)
- //{
- // return processInfo;
- //}
+ ProcessInfo processInfo = TryGetProcessInfo2();
+ if (null != processInfo)
+ {
+ return processInfo;
+ }
IpcMessage request = CreateProcessInfoMessage();
using IpcResponse response = IpcClient.SendMessageGetContinuation(_endpoint, request);
internal async Task<ProcessInfo> GetProcessInfoAsync(CancellationToken token)
{
- // RE: https://github.com/dotnet/runtime/issues/54083
- // If the GetProcessInfo2 command is sent too early, it will crash the runtime instance.
- // Disable the usage of the command until that issue is fixed.
-
// Attempt to get ProcessInfo v2
- //ProcessInfo processInfo = await TryGetProcessInfo2Async(token);
- //if (null != processInfo)
- //{
- // return processInfo;
- //}
+ ProcessInfo processInfo = await TryGetProcessInfo2Async(token);
+ if (null != processInfo)
+ {
+ return processInfo;
+ }
IpcMessage request = CreateProcessInfoMessage();
using IpcResponse response = await IpcClient.SendMessageGetContinuationAsync(_endpoint, request, token).ConfigureAwait(false);
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Runtime.InteropServices;
+using Xunit.Abstractions;
+
+namespace Microsoft.Diagnostics.NETCore.Client
+{
+ internal static class DiagnosticPortsHelper
+ {
+ private const string DiagnosticPortsEnvName = "DOTNET_DiagnosticPorts";
+ private const string DefaultDiagnosticPortSuspendEnvName = "DOTNET_DefaultDiagnosticPortSuspend";
+
+ /// <summary>
+ /// Creates a unique server name to avoid collisions from simultaneous running tests
+ /// or potentially abandoned socket files.
+ /// </summary>
+ public static string CreateServerTransportName()
+ {
+ string transportName = "DOTNET_DIAGSERVER_TESTS_" + Path.GetRandomFileName();
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ return transportName;
+ }
+ else
+ {
+ return Path.Combine(Path.GetTempPath(), transportName);
+ }
+ }
+
+ public static void SetDiagnosticPort(this TestRunner runner, string transportName, bool suspend)
+ {
+ string suspendArgument = suspend ? "suspend" : "nosuspend";
+ runner.AddEnvVar(DiagnosticPortsEnvName, $"{transportName},connect,{suspendArgument};");
+ }
+
+ public static void SuspendDefaultDiagnosticPort(this TestRunner runner)
+ {
+ runner.AddEnvVar(DefaultDiagnosticPortSuspendEnvName, "1");
+ }
+ }
+}
{
public class GetProcessInfoTests
{
- private readonly ITestOutputHelper output;
+ private readonly ITestOutputHelper _output;
public GetProcessInfoTests(ITestOutputHelper outputHelper)
{
- output = outputHelper;
+ _output = outputHelper;
}
[Fact]
- public Task BasicProcessInfoTest()
+ public Task BasicProcessInfoNoSuspendTest()
{
- return BasicProcessInfoTestCore(useAsync: false);
+ return BasicProcessInfoTestCore(useAsync: false, suspend: false);
}
[Fact]
- public Task BasicProcessInfoTestAsync()
+ public Task BasicProcessInfoNoSuspendTestAsync()
{
- return BasicProcessInfoTestCore(useAsync: true);
+ return BasicProcessInfoTestCore(useAsync: true, suspend: false);
}
- private async Task BasicProcessInfoTestCore(bool useAsync)
+ [Fact]
+ public Task BasicProcessInfoSuspendTest()
+ {
+ return BasicProcessInfoTestCore(useAsync: false, suspend: true);
+ }
+
+ [Fact]
+ public Task BasicProcessInfoSuspendTestAsync()
+ {
+ return BasicProcessInfoTestCore(useAsync: true, suspend: true);
+ }
+
+ private async Task BasicProcessInfoTestCore(bool useAsync, bool suspend)
{
- using TestRunner runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), output);
+ using TestRunner runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), _output);
+ if (suspend)
+ {
+ runner.SuspendDefaultDiagnosticPort();
+ }
runner.Start();
try
{
DiagnosticsClientApiShim clientShim = new DiagnosticsClientApiShim(new DiagnosticsClient(runner.Pid), useAsync);
- ProcessInfo processInfo = await clientShim.GetProcessInfo();
+ // While suspended, the runtime will not provide entrypoint information.
+ if (suspend)
+ {
+ ProcessInfo processInfoBeforeResume = await clientShim.GetProcessInfo();
+ ValidateProcessInfo(runner.Pid, processInfoBeforeResume);
+ Assert.True(string.IsNullOrEmpty(processInfoBeforeResume.ManagedEntrypointAssemblyName));
- Assert.NotNull(processInfo);
- Assert.Equal(runner.Pid, (int)processInfo.ProcessId);
- Assert.NotNull(processInfo.CommandLine);
- Assert.NotNull(processInfo.OperatingSystem);
- Assert.NotNull(processInfo.ProcessArchitecture);
- //Assert.Equal("Tracee", processInfo.ManagedEntrypointAssemblyName);
- //Version clrVersion = ParseVersionRemoveLabel(processInfo.ClrProductVersionString);
- //Assert.True(clrVersion >= new Version(6, 0, 0));
+ await clientShim.ResumeRuntime();
+ }
+
+ // The entrypoint information is available some short time after the runtime
+ // begins to execute. Retry getting process information until entrypoint is available.
+ ProcessInfo processInfo = await GetProcessInfoWithEntrypointAsync(clientShim);
+ ValidateProcessInfo(runner.Pid, processInfo);
+ Assert.Equal("Tracee", processInfo.ManagedEntrypointAssemblyName);
}
finally
{
}
}
+ /// <summary>
+ /// Get process information with entrypoint information with exponential backoff on retries.
+ /// </summary>
+ private async Task<ProcessInfo> GetProcessInfoWithEntrypointAsync(DiagnosticsClientApiShim shim)
+ {
+ int retryMilliseconds = 5;
+ int currentAttempt = 1;
+ const int maxAttempts = 10;
+
+ _output.WriteLine("Getting process info with entrypoint:");
+ while (currentAttempt <= maxAttempts)
+ {
+ _output.WriteLine("- Attempt {0} of {1}.", currentAttempt, maxAttempts);
+
+ ProcessInfo processInfo = await shim.GetProcessInfo();
+ Assert.NotNull(processInfo);
+
+ if (!string.IsNullOrEmpty(processInfo.ManagedEntrypointAssemblyName))
+ {
+ _output.WriteLine("Got process info with entrypoint.");
+ return processInfo;
+ }
+
+ currentAttempt++;
+
+ if (currentAttempt != maxAttempts)
+ {
+ _output.WriteLine(" Waiting {0} ms.", retryMilliseconds);
+
+ await Task.Delay(retryMilliseconds);
+
+ retryMilliseconds = Math.Min(2 * retryMilliseconds, 500);
+ }
+ }
+
+ throw new InvalidOperationException("Unable to get process info with entrypoint.");
+ }
+
+ private static void ValidateProcessInfo(int expectedProcessId, ProcessInfo processInfo)
+ {
+ Assert.NotNull(processInfo);
+ Assert.Equal(expectedProcessId, (int)processInfo.ProcessId);
+ Assert.NotNull(processInfo.CommandLine);
+ Assert.NotNull(processInfo.OperatingSystem);
+ Assert.NotNull(processInfo.ProcessArchitecture);
+ Version clrVersion = ParseVersionRemoveLabel(processInfo.ClrProductVersionString);
+ Assert.True(clrVersion >= new Version(6, 0, 0));
+ }
+
private static Version ParseVersionRemoveLabel(string versionString)
{
Assert.NotNull(versionString);
TestRunner runner = new TestRunner(commandLine, outputHelper, redirectError: true, redirectInput: true);
if (!string.IsNullOrEmpty(reversedServerTransportName))
{
- runner.AddReversedServer(reversedServerTransportName);
+ runner.SetDiagnosticPort(reversedServerTransportName, suspend: false);
}
runner.Start(testProcessTimeout: 60_000);
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.IO;
-using System.Runtime.InteropServices;
-using Xunit.Abstractions;
-
-namespace Microsoft.Diagnostics.NETCore.Client
-{
- internal static class ReversedServerHelper
- {
- /// <summary>
- /// Creates a unique server name to avoid collisions from simultaneous running tests
- /// or potentially abandoned socket files.
- /// </summary>
- public static string CreateServerTransportName()
- {
- string transportName = "DOTNET_DIAGSERVER_TESTS_" + Path.GetRandomFileName();
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- {
- return transportName;
- }
- else
- {
- return Path.Combine(Path.GetTempPath(), transportName);
- }
- }
-
- /// <summary>
- /// Starts the Tracee executable while enabling connection to reverse diagnostics server.
- /// </summary>
- public static TestRunner StartTracee(ITestOutputHelper _outputHelper, string transportName)
- {
- var runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), _outputHelper);
- runner.AddReversedServer(transportName);
- runner.Start();
- return runner;
- }
-
- public static void AddReversedServer(this TestRunner runner, string transportName)
- {
- runner.AddEnvVar("DOTNET_DiagnosticsMonitorAddress", transportName);
- runner.AddEnvVar("DOTNET_DiagnosticPorts", $"{transportName},nosuspend;");
- }
- }
-}
private ReversedDiagnosticsServer CreateReversedServer(out string transportName)
{
- transportName = ReversedServerHelper.CreateServerTransportName();
+ transportName = DiagnosticPortsHelper.CreateServerTransportName();
_outputHelper.WriteLine("Starting reversed server at '" + transportName + "'.");
return new ReversedDiagnosticsServer(transportName);
}
private TestRunner StartTracee(string transportName)
{
_outputHelper.WriteLine("Starting tracee.");
- return ReversedServerHelper.StartTracee(_outputHelper, transportName);
+ var runner = new TestRunner(CommonHelper.GetTraceePathWithArgs(targetFramework: "net5.0"), _outputHelper);
+ runner.SetDiagnosticPort(transportName, suspend: true);
+ runner.Start();
+ return runner;
}
private async Task VerifyWaitForConnection(IpcEndpointInfo info, bool useAsync, bool expectTimeout = false)