From 22007d7b6ac11118cfbe613b8281fdadf125fd7c Mon Sep 17 00:00:00 2001 From: Nullpointer Date: Wed, 3 Jul 2024 11:15:03 +0200 Subject: [PATCH] GD-124: Use `LaunchProcessWithDebuggerAttached` instead of `AttachDebuggerIfNeed` for Rider 2024.2 (#130) --- api/src/api/TestAdapterReporter.cs | 1 + gdUnit4Net.sln.DotSettings.user | 11 ++- test/project.godot | 2 +- testadapter/src/execution/TestExecutor.cs | 98 +++++++++++++++-------- 4 files changed, 78 insertions(+), 34 deletions(-) diff --git a/api/src/api/TestAdapterReporter.cs b/api/src/api/TestAdapterReporter.cs index f481103e..861bab51 100644 --- a/api/src/api/TestAdapterReporter.cs +++ b/api/src/api/TestAdapterReporter.cs @@ -33,6 +33,7 @@ public TestAdapterReporter() public void Dispose() { + Console.WriteLine("TestAdapterReporter: Disconnecting from GdUnit4 test report server."); writer?.WriteLine("TestAdapterReporter: Disconnecting from GdUnit4 test report server."); writer?.Dispose(); client.Dispose(); diff --git a/gdUnit4Net.sln.DotSettings.user b/gdUnit4Net.sln.DotSettings.user index 837f21cb..48075f1c 100644 --- a/gdUnit4Net.sln.DotSettings.user +++ b/gdUnit4Net.sln.DotSettings.user @@ -1,6 +1,6 @@  True - <SessionState ContinuousTestingMode="0" IsActive="True" Name="BoolAssertTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> + <SessionState ContinuousTestingMode="0" IsActive="True" Name="BoolAssertTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"> <TestAncestor> <TestId>VsTest::B97C5043-B4BE-4156-BE0F-FDCBDECA61F6::net8.0::executor://gdunit4.testadapter/#GdUnit4.Tests.Asserts.BoolAssertTest</TestId> </TestAncestor> @@ -8,5 +8,14 @@ + + + + + + + + + TRACE D:\development\workspace\gdUnit4Net\test\.runsettings \ No newline at end of file diff --git a/test/project.godot b/test/project.godot index cc04876a..50237ec0 100644 --- a/test/project.godot +++ b/test/project.godot @@ -11,7 +11,7 @@ config_version=5 [application] config/name="gdUnit4Test" -config/features=PackedStringArray("4.2", "4.2.1", "C#", "Forward Plus") +config/features=PackedStringArray("4.3", "C#", "Forward Plus") config/icon="res://icon.svg" [dotnet] diff --git a/testadapter/src/execution/TestExecutor.cs b/testadapter/src/execution/TestExecutor.cs index 3fed4248..1c03b3da 100644 --- a/testadapter/src/execution/TestExecutor.cs +++ b/testadapter/src/execution/TestExecutor.cs @@ -12,6 +12,7 @@ namespace GdUnit4.TestAdapter.Execution; using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using Microsoft.Win32.SafeHandles; using Settings; @@ -34,6 +35,9 @@ public TestExecutor(RunConfiguration configuration, GdUnit4Settings gdUnit4Setti this.gdUnit4Settings = gdUnit4Settings; } + private object CancelLock { get; } = new(); + private object ProcessLock { get; } = new(); + #pragma warning disable IDE0052 // Remove unread private members private int ParallelTestCount { get; set; } #pragma warning restore IDE0052 // Remove unread private members @@ -42,7 +46,7 @@ public TestExecutor(RunConfiguration configuration, GdUnit4Settings gdUnit4Setti public void Cancel() { - lock (this) + lock (CancelLock) { Console.WriteLine("Cancel triggered"); try @@ -52,16 +56,13 @@ public void Cancel() } catch (Exception) { - //frameworkHandle.SendMessage(TestMessageLevel.Error, @$"TestRunner ends with: {e.Message}"); + //fh.SendMessage(TestMessageLevel.Error, @$"TestRunner ends with: {e.Message}"); } } } public void Dispose() - { - pProcess?.Dispose(); - GC.SuppressFinalize(this); - } + => pProcess?.Dispose(); public void Run(IFrameworkHandle frameworkHandle, IRunContext runContext, IReadOnlyList testCases) { @@ -85,7 +86,8 @@ public void Run(IFrameworkHandle frameworkHandle, IRunContext runContext, IReadO var debugArg = runContext.IsBeingDebugged ? "-d" : ""; using var eventServer = new TestEventReportServer(); - Task.Run(() => eventServer.Start(frameworkHandle, testCases)); + // ReSharper disable once AccessToDisposedClosure + var testEventServerTask = Task.Run(() => eventServer.Start(frameworkHandle, testCases)); //var filteredTestCases = filterExpression != null // ? testCases.FindAll(t => filterExpression.MatchTestCase(t, (propertyName) => @@ -94,7 +96,7 @@ public void Run(IFrameworkHandle frameworkHandle, IRunContext runContext, IReadO // return t.GetPropertyValue(testProperty); // }) == false) // : testCases; - var testRunnerScene = "res://gdunit4_testadapter/TestAdapterRunner.tscn"; //Path.Combine(workingDirectory, @$"{temp_test_runner_dir}/TestRunner.tscn"); + var testRunnerScene = "res://gdunit4_testadapter/TestAdapterRunner.tscn"; var arguments = $"{debugArg} --path . {testRunnerScene} --testadapter --configfile=\"{configName}\" {gdUnit4Settings.Parameters}"; frameworkHandle.SendMessage(TestMessageLevel.Informational, @$"Run with args {arguments}"); var processStartInfo = new ProcessStartInfo(@$"{GodotBin}", arguments) @@ -109,30 +111,62 @@ public void Run(IFrameworkHandle frameworkHandle, IRunContext runContext, IReadO WorkingDirectory = @$"{workingDirectory}" }; - using (pProcess = new Process { StartInfo = processStartInfo }) - { - pProcess.EnableRaisingEvents = true; - pProcess.ErrorDataReceived += StdErrorProcessor(frameworkHandle); - pProcess.Exited += ExitHandler(frameworkHandle); - pProcess.Start(); - pProcess.BeginErrorReadLine(); - pProcess.BeginOutputReadLine(); - AttachDebuggerIfNeed(runContext, frameworkHandle, pProcess); - while (!pProcess.WaitForExit(SessionTimeOut)) - Thread.Sleep(100); - try - { - pProcess.Kill(true); - frameworkHandle.SendMessage(TestMessageLevel.Informational, @$"Run TestRunner ends with {pProcess.ExitCode}"); - } - catch (Exception e) - { - frameworkHandle.SendMessage(TestMessageLevel.Error, @$"Run TestRunner ends with: {e.Message}"); - } - finally + lock (ProcessLock) + if (runContext.IsBeingDebugged && frameworkHandle is IFrameworkHandle2 fh2 && fh2.GetType().ToString().Contains("JetBrains") && + fh2.GetType().Assembly.GetName().Version >= new Version("2.16.1.14")) { + frameworkHandle.SendMessage(TestMessageLevel.Informational, $"JetBrains Rider detected {fh2.GetType().Assembly.GetName().Version}"); + RunDebugRider(fh2, processStartInfo); File.Delete(configName); } + else + using (pProcess = new Process { StartInfo = processStartInfo }) + try + { + pProcess.EnableRaisingEvents = true; + pProcess.ErrorDataReceived += StdErrorProcessor(frameworkHandle); + pProcess.Exited += ExitHandler(frameworkHandle); + pProcess.Start(); + pProcess.BeginErrorReadLine(); + pProcess.BeginOutputReadLine(); + AttachDebuggerIfNeed(runContext, frameworkHandle, pProcess); + pProcess.WaitForExit(SessionTimeOut); + frameworkHandle.SendMessage(TestMessageLevel.Informational, @$"Run TestRunner ends with {pProcess.ExitCode}"); + pProcess.Kill(true); + } + catch (Exception e) + { + frameworkHandle.SendMessage(TestMessageLevel.Error, @$"Run TestRunner ends with: {e.Message}"); + } + finally { File.Delete(configName); } + + // wait until all event messages are processed or the client is disconnected + testEventServerTask.Wait(TimeSpan.FromSeconds(2)); + } + + private void RunDebugRider(IFrameworkHandle2 fh2, ProcessStartInfo psi) + { + // EnableShutdownAfterTestRun is not working we need to use SafeHandle the get the process running until the ExitCode is getted + fh2.EnableShutdownAfterTestRun = true; + Console.WriteLine($"Debug process started {psi.FileName} {psi.WorkingDirectory} {psi.Arguments}"); + var processId = fh2.LaunchProcessWithDebuggerAttached(psi.FileName, psi.WorkingDirectory, psi.Arguments, psi.Environment); + pProcess = Process.GetProcessById(processId); + SafeProcessHandle? processHandle = null; + try + { + processHandle = pProcess.SafeHandle; + var isExited = pProcess.WaitForExit(SessionTimeOut); + // it never exits on macOS ? + Console.WriteLine($"Process exited: HasExited: {pProcess.HasExited} {isExited} {processHandle}"); + // enforce kill the process has also no affect on macOS + pProcess.Kill(true); + Console.WriteLine($"Process exited: HasExited: {pProcess.HasExited} {processHandle.IsClosed}"); + // this line fails on macOS, maybe the SafeHandle works only on windows + //fh2.SendMessage(TestMessageLevel.Informational, @$"Run TestRunner ends with {pProcess.ExitCode}"); + } + finally + { + processHandle?.Dispose(); } } @@ -141,7 +175,7 @@ private void InstallTestRunnerAndBuild(IFrameworkHandle frameworkHandle, string var destinationFolderPath = Path.Combine(workingDirectory, @$"{TempTestRunnerDir}"); if (Directory.Exists(destinationFolderPath)) return; - frameworkHandle.SendMessage(TestMessageLevel.Informational, "Install GdUnit4 TestRunner"); + frameworkHandle.SendMessage(TestMessageLevel.Informational, $"Installing GdUnit4 `TestRunner` at {destinationFolderPath}..."); InstallTestRunnerClasses(destinationFolderPath); var processStartInfo = new ProcessStartInfo(@$"{GodotBin}", @"--path . --headless --build-solutions --quit-after 20") { @@ -162,11 +196,11 @@ private void InstallTestRunnerAndBuild(IFrameworkHandle frameworkHandle, string try { process.Kill(true); - frameworkHandle.SendMessage(TestMessageLevel.Informational, $"TestRunner installed: {process.ExitCode}"); + frameworkHandle.SendMessage(TestMessageLevel.Informational, $"GdUnit4 `TestRunner` successfully installed: {process.ExitCode}"); } catch (Exception e) { - frameworkHandle.SendMessage(TestMessageLevel.Error, @$"TestRunner ends with: {e.Message}"); + frameworkHandle.SendMessage(TestMessageLevel.Error, @$"Install GdUnit4 `TestRunner` ends with: {e.Message}"); } }