diff --git a/CHANGELOG.md b/CHANGELOG.md index 18ff770..569623f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.6.0] / 2024-09-25 - 2024-10-06 +### Features +- Support `TestAdapter` custom executer. +- Support environment variables to overwrite settings in the `TestAdapter`. (Fix: #57) +- Support `Cancel` tests and `Kill` processes in the `TestAdapter`. +### App +- Force to kill test when `ClientDisconnected`. +### Command +- Update to use `JsonService` implementation to use `Newtonsoft.Json` in the domain. +- Update `ProcessStart` with `GetProcesses` to enable kill process. +### Console +- Add `RevitTestUtils.Exceptions` to show exceptions in the console. +- Add `ricaun.RevitAPI.Fake.References.RevitAPIUI` to fix `RevitAPIUI` reference exception in the discovery test. (Fix: #58) +- Disable/Remove discovery test to use `RevitAPIUI` version `2021` to `2023`. +- Add Environment variable for process arguments `RICAUN_REVITTEST_CONSOLE_PROCESS_ARGUMENTS`. +### Shared +- Update to use `ricaun.NamedPipeWrapper.Json` version `1.8.0`. +- Add `ClientDisconnected` in `PipeProcessServer`. +### TestAdapter +- Use `LocalExtensionData` to store `TestModel` result. +- Add `EnvironmentSettings` to overwrite settings in the `TestAdapter`. +- Update `JsonService` implementation to use `Newtonsoft.Json` in the domain. +- Support `Cancel` to abort and `Kill` processes. +- Add `ProcessExtension` to `KillTree` for net framework. +### Tests +- Add `TestsRevitUI` to validate fake `RevitAPIUI` reference. + ## [1.5.0] / 2024-09-11 - 2024-09-19 ### Features - Support tests with `TestCaseSource`. (Fix: #55) @@ -486,6 +513,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - [x] TestsFail [vNext]: ../../compare/1.0.0...HEAD +[1.6.0]: ../../compare/1.5.0...1.6.0 [1.5.0]: ../../compare/1.4.1...1.5.0 [1.4.1]: ../../compare/1.4.0...1.4.1 [1.4.0]: ../../compare/1.3.6...1.4.0 diff --git a/Directory.Build.props b/Directory.Build.props index 019c277..19b00f1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,5 +1,5 @@ - 1.5.0 + 1.6.0 \ No newline at end of file diff --git a/README.old.md b/README.old.md index 6782573..2a1fdc3 100644 --- a/README.old.md +++ b/README.old.md @@ -69,7 +69,7 @@ For more information see [Wiki](https://github.com/ricaun-io/ricaun.RevitTest/wi * [ricaun.RevitTest.Application.bundle.zip](ricaun.RevitTest.Application) #### Shared * [ricaun.NUnit](https://github.com/ricaun-io/ricaun.NUnit) -* [NamedPipeWrapper.Json](https://github.com/ricaun-io/named-pipe-wrapper-json) +* [ricaun.NamedPipeWrapper.Json](https://github.com/ricaun-io/named-pipe-wrapper-json) #### TestAdapter * [ricaun.RevitTest.Console.exe](ricaun.RevitTest.Console) * [ricaun.RevitTest.Command](ricaun.RevitTest.Command) @@ -131,6 +131,18 @@ dotnet test ricaun.RevitTest.Tests.dll --settings:.runsettings -- NUnit.Version= dotnet test ricaun.RevitTest.Tests.dll -v:detailed -- NUnit.Application="" ``` +### Environment Variables +``` +RICAUN_REVITTEST_TESTADAPTER_NUNIT_VERSION +RICAUN_REVITTEST_TESTADAPTER_NUNIT_LANGUAGE +RICAUN_REVITTEST_TESTADAPTER_NUNIT_OPEN +RICAUN_REVITTEST_TESTADAPTER_NUNIT_CLOSE +RICAUN_REVITTEST_TESTADAPTER_NUNIT_TIMEOUT +RICAUN_REVITTEST_TESTADAPTER_NUNIT_VERBOSITY +RICAUN_REVITTEST_TESTADAPTER_NUNIT_APPLICATION +RICAUN_REVITTEST_TESTADAPTER_NUNIT_METADATA +``` + ### `.runsettings` .csproj ```xml diff --git a/ricaun.RevitTest.Application/Revit/App.cs b/ricaun.RevitTest.Application/Revit/App.cs index c0ce3af..a289911 100644 --- a/ricaun.RevitTest.Application/Revit/App.cs +++ b/ricaun.RevitTest.Application/Revit/App.cs @@ -73,6 +73,22 @@ private static void PipeTestServer_Initialize() response.Tests = null; }); + PipeTestServer.ClientDisconnected = () => + { + try + { + if (IsTestRunning) + { + Log.WriteLine("PipeTestServer: ClientDisconnected - TestEngine.KillTests"); + ricaun.NUnit.TestEngine.Result = new TestModelResult((test) => + { + throw new Exception("Client disconnect, TestEngine.Kill to ignore test result."); + }); + } + } + catch { } + }; + var initializeServer = PipeTestServer.Initialize(); if (initializeServer) diff --git a/ricaun.RevitTest.Command/Extensions/Json/IJsonService.cs b/ricaun.RevitTest.Command/Extensions/Json/IJsonService.cs new file mode 100644 index 0000000..6347497 --- /dev/null +++ b/ricaun.RevitTest.Command/Extensions/Json/IJsonService.cs @@ -0,0 +1,23 @@ +namespace ricaun.RevitTest.Command.Extensions.Json +{ + /// + /// Represents a service for JSON serialization and deserialization. + /// + public interface IJsonService + { + /// + /// Deserializes the specified JSON string into an object of type T. + /// + /// The type of the object to deserialize. + /// The JSON string to deserialize. + /// The deserialized object of type T. + T Deserialize(string value); + + /// + /// Serializes the specified object into a JSON string. + /// + /// The object to serialize. + /// The JSON string representation of the serialized object. + string Serialize(object value); + } +} \ No newline at end of file diff --git a/ricaun.RevitTest.Command/Extensions/Json/JsonService.cs b/ricaun.RevitTest.Command/Extensions/Json/JsonService.cs new file mode 100644 index 0000000..4082906 --- /dev/null +++ b/ricaun.RevitTest.Command/Extensions/Json/JsonService.cs @@ -0,0 +1,43 @@ +namespace ricaun.RevitTest.Command.Extensions.Json +{ +#if NETFRAMEWORK + using System.Web.Script.Serialization; + internal class JsonService : IJsonService + { + public JsonService() + { + JavaScriptSerializer = new JavaScriptSerializer(); + } + public JavaScriptSerializer JavaScriptSerializer { get; set; } + public T Deserialize(string value) + { + return JavaScriptSerializer.Deserialize(value); + } + + public string Serialize(object value) + { + return JavaScriptSerializer.Serialize(value); + } + } +#endif +#if NET + using System.Text.Json; + internal class JsonService : IJsonService + { + public T Deserialize(string value) + { + return JsonSerializer.Deserialize(value); + } + + public string Serialize(object value) + { + return JsonSerializer.Serialize(value); + } + } +#endif +#if NETSTANDARD +internal class JsonService : NewtonsoftJsonService +{ +} +#endif +} \ No newline at end of file diff --git a/ricaun.RevitTest.Command/Extensions/Json/NewtonsoftJsonService.cs b/ricaun.RevitTest.Command/Extensions/Json/NewtonsoftJsonService.cs new file mode 100644 index 0000000..f94a5c5 --- /dev/null +++ b/ricaun.RevitTest.Command/Extensions/Json/NewtonsoftJsonService.cs @@ -0,0 +1,20 @@ +namespace ricaun.RevitTest.Command.Extensions.Json +{ + internal class NewtonsoftJsonService : IJsonService + { + public NewtonsoftJsonService() + { + Settings = new Newtonsoft.Json.JsonSerializerSettings(); + } + public Newtonsoft.Json.JsonSerializerSettings Settings { get; } + public T Deserialize(string value) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(value, Settings); + } + + public string Serialize(object value) + { + return Newtonsoft.Json.JsonConvert.SerializeObject(value, Settings); + } + } +} \ No newline at end of file diff --git a/ricaun.RevitTest.Command/Extensions/JsonExtension.cs b/ricaun.RevitTest.Command/Extensions/JsonExtension.cs index ed390ae..50c5777 100644 --- a/ricaun.RevitTest.Command/Extensions/JsonExtension.cs +++ b/ricaun.RevitTest.Command/Extensions/JsonExtension.cs @@ -1,98 +1,33 @@ -namespace ricaun.RevitTest.Command.Extensions +using ricaun.RevitTest.Command.Extensions.Json; + +namespace ricaun.RevitTest.Command.Extensions { -#if NETFRAMEWORK - using System.Web.Script.Serialization; /// /// JsonExtension - /// Reference Include="System.Web.Extensions" /// public static class JsonExtension { - private static JavaScriptSerializer JavaScriptSerializer { get; set; } = new JavaScriptSerializer(); - - /// - /// Deserialize - /// - /// - /// - /// - public static T Deserialize(this string value) - { - return JavaScriptSerializer.Deserialize(value); - } - /// - /// Serialize + /// IJsonService /// - /// - /// - public static string Serialize(object value) - { - return JavaScriptSerializer.Serialize(value); - } + public static IJsonService JsonService { get; set; } = CreateJsonService(); /// - /// ToJson + /// Create JsonService /// - /// /// - public static string ToJson(this object value) + /// + /// If NewtonsoftJsonService is available, use it. + /// + private static IJsonService CreateJsonService() { - return Serialize(value); + try + { + return new NewtonsoftJsonService(); + } + catch { }; + return new JsonService(); } - } -#endif - -#if NETSTANDARD - using Newtonsoft.Json; - public static class JsonExtension - { - /// - /// Settings - /// - public static JsonSerializerSettings Settings { get; set; } = new JsonSerializerSettings(); - - /// - /// Deserialize - /// - /// - /// - /// - public static T Deserialize(this string value) - { - return JsonConvert.DeserializeObject(value, Settings); - } - - /// - /// Serialize - /// - /// - /// - public static string Serialize(object value) - { - return JsonConvert.SerializeObject(value, Settings); - } - - /// - /// ToJson - /// - /// - /// - public static string ToJson(this object value) - { - return Serialize(value); - } - } -#endif - -#if NET - using System.Text.Json; - public static class JsonExtension - { - /// - /// Settings - /// - public static JsonSerializerOptions Settings { get; set; } = new JsonSerializerOptions(); /// /// Deserialize @@ -102,7 +37,7 @@ public static class JsonExtension /// public static T Deserialize(this string value) { - return JsonSerializer.Deserialize(value, Settings); + return JsonService.Deserialize(value); } /// @@ -112,7 +47,7 @@ public static T Deserialize(this string value) /// public static string Serialize(object value) { - return JsonSerializer.Serialize(value, Settings); + return JsonService.Serialize(value); } /// @@ -125,5 +60,4 @@ public static string ToJson(this object value) return Serialize(value); } } -#endif } \ No newline at end of file diff --git a/ricaun.RevitTest.Command/Process/ProcessStart.cs b/ricaun.RevitTest.Command/Process/ProcessStart.cs index 6ce0847..9f52729 100644 --- a/ricaun.RevitTest.Command/Process/ProcessStart.cs +++ b/ricaun.RevitTest.Command/Process/ProcessStart.cs @@ -10,6 +10,12 @@ namespace ricaun.RevitTest.Command.Process { public class ProcessStart { + private static List Processes = new List(); + public static System.Diagnostics.Process[] GetProcesses() + { + return Processes.OfType().Where(e => !e.HasExited).ToArray(); + } + protected virtual void WriteLine(string message) { Debug.WriteLine(message); @@ -99,6 +105,7 @@ private async Task Run(string arguments, if (string.IsNullOrEmpty(processPath)) return; var psi = NewProcessStartInfo(arguments); var process = System.Diagnostics.Process.Start(psi); + Processes.Add(process); process.OutputDataReceived += (sender, e) => { consoleAction?.Invoke(e.Data); diff --git a/ricaun.RevitTest.Command/ricaun.RevitTest.Command.csproj b/ricaun.RevitTest.Command/ricaun.RevitTest.Command.csproj index da4013d..be4d7ad 100644 --- a/ricaun.RevitTest.Command/ricaun.RevitTest.Command.csproj +++ b/ricaun.RevitTest.Command/ricaun.RevitTest.Command.csproj @@ -90,6 +90,12 @@ + + + NU1903 + + + diff --git a/ricaun.RevitTest.Console/Resources/ricaun.RevitTest.Application.bundle.zip b/ricaun.RevitTest.Console/Resources/ricaun.RevitTest.Application.bundle.zip index b5e8079..94635c0 100644 Binary files a/ricaun.RevitTest.Console/Resources/ricaun.RevitTest.Application.bundle.zip and b/ricaun.RevitTest.Console/Resources/ricaun.RevitTest.Application.bundle.zip differ diff --git a/ricaun.RevitTest.Console/Revit/Utils/RevitTestUtils.cs b/ricaun.RevitTest.Console/Revit/Utils/RevitTestUtils.cs index e2611aa..940c663 100644 --- a/ricaun.RevitTest.Console/Revit/Utils/RevitTestUtils.cs +++ b/ricaun.RevitTest.Console/Revit/Utils/RevitTestUtils.cs @@ -19,9 +19,6 @@ namespace ricaun.RevitTest.Console.Revit.Utils /// public static class RevitTestUtils { - private const int RevitMinVersionReference = 2021; - private const int RevitMaxVersionReference = 2023; - private const int SleepMillisecondsBeforeFinish = 1000; private const int SleepMillisecondsDebuggerAttached = 1000; @@ -40,22 +37,17 @@ public static string[] GetTestFullNames(string filePath) Log.WriteLine($"RevitTestUtils: {revitVersion}"); LoggerTest($"RevitTestUtils: {revitVersion}"); - // Problem with AnavRes.dll / adui22res.dll (version -2020) - // Problem with UI (version 2024) - var revitVersionMinMax = Math.Min(Math.Max(revitVersion, RevitMinVersionReference), RevitMaxVersionReference); - if (RevitInstallationUtils.InstalledRevit.TryGetRevitInstallationGreater(revitVersionMinMax, out RevitInstallation revitInstallationMinMax)) - { - LoggerTest($"GetTest Version {revitVersionMinMax}"); - Log.WriteLine($"RevitTestUtils: {revitInstallationMinMax.InstallLocation}"); - tests = TestEngine.GetTestFullNames(filePath, revitInstallationMinMax.InstallLocation); - } - else if (RevitInstallationUtils.InstalledRevit.TryGetRevitInstallationGreater(revitVersion, out RevitInstallation revitInstallation)) + if (RevitInstallationUtils.InstalledRevit.TryGetRevitInstallationGreater(revitVersion, out RevitInstallation revitInstallation)) { LoggerTest($"GetTest Version {revitVersion}"); Log.WriteLine($"RevitTestUtils: {revitInstallation.InstallLocation}"); tests = TestEngine.GetTestFullNames(filePath, revitInstallation.InstallLocation); } + foreach (var exception in TestEngine.Exceptions) + { + Log.WriteLine($"RevitTestUtils.Exceptions: {exception}"); + } if (tests.Length == 0) { throw new Exception($"Unable to read tests class using the Revit version {revitVersion}."); @@ -111,7 +103,7 @@ public static void CreateRevitServer( { int timeoutNotBusyCountMax = 10; - if (timeoutMinutes <= 0) + if (timeoutMinutes <= 0) timeoutMinutes = TimeoutMinutesDefault; if (revitVersionNumber == 0) @@ -162,6 +154,7 @@ public static void CreateRevitServer( if (revitInstallation.TryGetProcess(out Process process) == false || forceToOpenNewRevit) { var startRevitLanguageArgument = RevitLanguageUtils.GetArgument(forceLanguageToRevit); + startRevitLanguageArgument = string.Join(" ", startRevitLanguageArgument, Environment.GetEnvironmentVariable("RICAUN_REVITTEST_CONSOLE_PROCESS_ARGUMENTS")); Log.WriteLine($"{revitInstallation}: Start {startRevitLanguageArgument}"); process = revitInstallation.Start(startRevitLanguageArgument); } diff --git a/ricaun.RevitTest.Console/ricaun.RevitTest.Console.csproj b/ricaun.RevitTest.Console/ricaun.RevitTest.Console.csproj index c4d8cd4..54b73cc 100644 --- a/ricaun.RevitTest.Console/ricaun.RevitTest.Console.csproj +++ b/ricaun.RevitTest.Console/ricaun.RevitTest.Console.csproj @@ -48,6 +48,7 @@ + diff --git a/ricaun.RevitTest.Shared/NamedPipeExtension.cs b/ricaun.RevitTest.Shared/NamedPipeExtension.cs index 6fcd75e..da92351 100644 --- a/ricaun.RevitTest.Shared/NamedPipeExtension.cs +++ b/ricaun.RevitTest.Shared/NamedPipeExtension.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper; +using ricaun.NamedPipeWrapper; using System; using System.Diagnostics; diff --git a/ricaun.RevitTest.Shared/PipeProcessClient.cs b/ricaun.RevitTest.Shared/PipeProcessClient.cs index a97e426..a7c2ceb 100644 --- a/ricaun.RevitTest.Shared/PipeProcessClient.cs +++ b/ricaun.RevitTest.Shared/PipeProcessClient.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper; +using ricaun.NamedPipeWrapper; using System; namespace ricaun.RevitTest.Shared diff --git a/ricaun.RevitTest.Shared/PipeProcessServer.cs b/ricaun.RevitTest.Shared/PipeProcessServer.cs index ead9220..faba00e 100644 --- a/ricaun.RevitTest.Shared/PipeProcessServer.cs +++ b/ricaun.RevitTest.Shared/PipeProcessServer.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper; +using ricaun.NamedPipeWrapper; using System; using System.Diagnostics; @@ -27,12 +27,20 @@ public bool Initialize() namedPipe = new NamedPipeServer(pipeName); namedPipe.ClientConnected += OnClientConnected; namedPipe.ClientMessage += OnClientMessage; + namedPipe.ClientDisconnected += OnClientDisconnected; namedPipe.NamedPipeDebug(); namedPipe.Start(); } return namedPipe != null; } + public Action ClientDisconnected { get; set; } + + private void OnClientDisconnected(NamedPipeConnection connection) + { + ClientDisconnected?.Invoke(); + } + public void Update(Action response) { if (ServerMessage is null) ServerMessage = new TServer(); diff --git a/ricaun.RevitTest.Shared/PipeTestServer.cs b/ricaun.RevitTest.Shared/PipeTestServer.cs index 50281ae..ebd19d8 100644 --- a/ricaun.RevitTest.Shared/PipeTestServer.cs +++ b/ricaun.RevitTest.Shared/PipeTestServer.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper; +using ricaun.NamedPipeWrapper; using System; using System.Diagnostics; diff --git a/ricaun.RevitTest.Shared/ProcessPipeNameUtils.cs b/ricaun.RevitTest.Shared/ProcessPipeNameUtils.cs index 81be872..6919446 100644 --- a/ricaun.RevitTest.Shared/ProcessPipeNameUtils.cs +++ b/ricaun.RevitTest.Shared/ProcessPipeNameUtils.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper; +using ricaun.NamedPipeWrapper; using System.Diagnostics; namespace ricaun.RevitTest.Shared diff --git a/ricaun.RevitTest.Shared/TestRequest.cs b/ricaun.RevitTest.Shared/TestRequest.cs index 88291ef..19129af 100644 --- a/ricaun.RevitTest.Shared/TestRequest.cs +++ b/ricaun.RevitTest.Shared/TestRequest.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper.Json; +using ricaun.NamedPipeWrapper.Json; using System; using System.ComponentModel; using System.Runtime.CompilerServices; diff --git a/ricaun.RevitTest.Shared/TestResponse.cs b/ricaun.RevitTest.Shared/TestResponse.cs index 5e9f6a0..4c43286 100644 --- a/ricaun.RevitTest.Shared/TestResponse.cs +++ b/ricaun.RevitTest.Shared/TestResponse.cs @@ -1,4 +1,4 @@ -using NamedPipeWrapper.Json; +using ricaun.NamedPipeWrapper.Json; using ricaun.NUnit.Models; using System; using System.ComponentModel; diff --git a/ricaun.RevitTest.Shared/ricaun.RevitTest.Shared.csproj b/ricaun.RevitTest.Shared/ricaun.RevitTest.Shared.csproj index 8868679..18ac533 100644 --- a/ricaun.RevitTest.Shared/ricaun.RevitTest.Shared.csproj +++ b/ricaun.RevitTest.Shared/ricaun.RevitTest.Shared.csproj @@ -64,7 +64,7 @@ - + diff --git a/ricaun.RevitTest.TestAdapter/Extensions/Json/JsonService.cs b/ricaun.RevitTest.TestAdapter/Extensions/Json/JsonService.cs index 6da24b7..2d7bb34 100644 --- a/ricaun.RevitTest.TestAdapter/Extensions/Json/JsonService.cs +++ b/ricaun.RevitTest.TestAdapter/Extensions/Json/JsonService.cs @@ -8,7 +8,11 @@ /// internal class JsonService : IJsonService { - private static JavaScriptSerializer JavaScriptSerializer { get; set; } = new JavaScriptSerializer(); + public JsonService() + { + JavaScriptSerializer = new JavaScriptSerializer(); + } + public JavaScriptSerializer JavaScriptSerializer { get; set; } public T Deserialize(string value) { return JavaScriptSerializer.Deserialize(value); @@ -19,7 +23,8 @@ public string Serialize(object value) return JavaScriptSerializer.Serialize(value); } } -#else +#endif +#if NET using System.Text.Json; internal class JsonService : IJsonService { diff --git a/ricaun.RevitTest.TestAdapter/Extensions/Json/NewtonsoftJsonService.cs b/ricaun.RevitTest.TestAdapter/Extensions/Json/NewtonsoftJsonService.cs new file mode 100644 index 0000000..490a49a --- /dev/null +++ b/ricaun.RevitTest.TestAdapter/Extensions/Json/NewtonsoftJsonService.cs @@ -0,0 +1,20 @@ +namespace ricaun.RevitTest.TestAdapter.Extensions.Json +{ + internal class NewtonsoftJsonService : IJsonService + { + public NewtonsoftJsonService() + { + Settings = new Newtonsoft.Json.JsonSerializerSettings(); + } + public Newtonsoft.Json.JsonSerializerSettings Settings { get; } + public T Deserialize(string value) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(value, Settings); + } + + public string Serialize(object value) + { + return Newtonsoft.Json.JsonConvert.SerializeObject(value, Settings); + } + } +} \ No newline at end of file diff --git a/ricaun.RevitTest.TestAdapter/Extensions/JsonExtension.cs b/ricaun.RevitTest.TestAdapter/Extensions/JsonExtension.cs index 73d8894..5b8e398 100644 --- a/ricaun.RevitTest.TestAdapter/Extensions/JsonExtension.cs +++ b/ricaun.RevitTest.TestAdapter/Extensions/JsonExtension.cs @@ -6,7 +6,27 @@ /// internal static class JsonExtension { - private static IJsonService jsonService { get; set; } = new JsonService(); + /// + /// IJsonService + /// + public static IJsonService JsonService { get; set; } = CreateJsonService(); + + /// + /// Create JsonService + /// + /// + /// + /// If NewtonsoftJsonService is available, use it. + /// + private static IJsonService CreateJsonService() + { + try + { + return new NewtonsoftJsonService(); + } + catch { }; + return new JsonService(); + } /// /// Deserialize @@ -16,7 +36,7 @@ internal static class JsonExtension /// public static T Deserialize(this string value) { - return jsonService.Deserialize(value); + return JsonService.Deserialize(value); } /// @@ -26,7 +46,7 @@ public static T Deserialize(this string value) /// public static string Serialize(object value) { - return jsonService.Serialize(value); + return JsonService.Serialize(value); } /// diff --git a/ricaun.RevitTest.TestAdapter/Extensions/ProcessExtension.cs b/ricaun.RevitTest.TestAdapter/Extensions/ProcessExtension.cs new file mode 100644 index 0000000..85b4c9d --- /dev/null +++ b/ricaun.RevitTest.TestAdapter/Extensions/ProcessExtension.cs @@ -0,0 +1,138 @@ +#if NETFRAMEWORK +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +namespace ricaun.RevitTest.TestAdapter.Extensions +{ + internal static class ProcessExtension + { + /// + /// Kill + /// + /// + /// + public static void Kill(this Process process, bool entireProcessTree) + { + if (process is null) return; + + if (!entireProcessTree) + { + process.Kill(); + return; + } + + process.KillTree(); + } + + #region KillTree + private static readonly bool _isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + private static readonly TimeSpan _defaultTimeout = TimeSpan.FromSeconds(30); + + /// + /// KillTree + /// + /// + /// + /// https://github.com/dotnet/cli/blob/master/test/Microsoft.DotNet.Tools.Tests.Utilities/Extensions/ProcessExtensions.cs + /// + internal static void KillTree(this Process process) + { + process.KillTree(_defaultTimeout); + } + + private static void KillTree(this Process process, TimeSpan timeout) + { + string stdout; + if (_isWindows) + { + RunProcessAndWaitForExit( + "taskkill", + $"/T /F /PID {process.Id}", + timeout, + out stdout); + } + else + { + var children = new HashSet(); + GetAllChildIdsUnix(process.Id, children, timeout); + foreach (var childId in children) + { + KillProcessUnix(childId, timeout); + } + KillProcessUnix(process.Id, timeout); + } + } + + private static void GetAllChildIdsUnix(int parentId, ISet children, TimeSpan timeout) + { + string stdout; + var exitCode = RunProcessAndWaitForExit( + "pgrep", + $"-P {parentId}", + timeout, + out stdout); + + if (exitCode == 0 && !string.IsNullOrEmpty(stdout)) + { + using (var reader = new StringReader(stdout)) + { + while (true) + { + var text = reader.ReadLine(); + if (text == null) + { + return; + } + + int id; + if (int.TryParse(text, out id)) + { + children.Add(id); + // Recursively get the children + GetAllChildIdsUnix(id, children, timeout); + } + } + } + } + } + + private static void KillProcessUnix(int processId, TimeSpan timeout) + { + string stdout; + RunProcessAndWaitForExit( + "kill", + $"-TERM {processId}", + timeout, + out stdout); + } + + private static int RunProcessAndWaitForExit(string fileName, string arguments, TimeSpan timeout, out string stdout) + { + var startInfo = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + RedirectStandardOutput = true, + UseShellExecute = false + }; + + var process = Process.Start(startInfo); + + stdout = null; + if (process.WaitForExit((int)timeout.TotalMilliseconds)) + { + stdout = process.StandardOutput.ReadToEnd(); + } + else + { + process.Kill(); + } + + return process.ExitCode; + } + #endregion + } +} +#endif \ No newline at end of file diff --git a/ricaun.RevitTest.TestAdapter/Metadatas/EnvironmentSettings.cs b/ricaun.RevitTest.TestAdapter/Metadatas/EnvironmentSettings.cs new file mode 100644 index 0000000..d2ac584 --- /dev/null +++ b/ricaun.RevitTest.TestAdapter/Metadatas/EnvironmentSettings.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; + +namespace ricaun.RevitTest.TestAdapter.Metadatas +{ + internal static class EnvironmentSettings + { + /// + /// Apply the environment settings. + /// + public static void Create() + { + try + { + var environmentDictionary = new Dictionary(); + var environmentKeyNames = GetEnvironmentKeyNames(); + foreach (var environmentKeyName in environmentKeyNames) + { + var environmentName = environmentKeyName.Value; + var value = Environment.GetEnvironmentVariable(environmentName); + + AdapterLogger.Logger.DebugOnlyLocal($"\tEnvironment: {environmentName} \t {value}"); + + if (value is null) + continue; + + environmentDictionary[environmentKeyName.Key] = value; + AdapterLogger.Logger.Info($"Environment: {environmentName}"); + } + + MapperKey.Map(AdapterSettings.Settings, environmentDictionary); + + AdapterLogger.Logger.DebugOnlyLocal($"\tAdapterSettings: {AdapterSettings.Settings}"); + } + catch (Exception ex) + { + AdapterLogger.Logger.InfoAny($"Environment: {ex}"); + } + } + + /// + /// Gets the environment names. + /// + /// The environment names. + /// + /// NUnit.Version -> RICAUN_REVITTEST_TESTADAPTER_NUNIT_VERSION + /// + public static IEnumerable GetEnvironmentNames() + { + return GetEnvironmentKeyNames().Values; + } + + /// + /// Gets the environment key names. + /// + /// The environment key names. + /// + /// NUnit.Version -> RICAUN_REVITTEST_TESTADAPTER_NUNIT_VERSION + /// + internal static Dictionary GetEnvironmentKeyNames() + { + var result = new Dictionary(); + var assemblyName = typeof(EnvironmentSettings).Assembly.GetName().Name; + var names = MapperKey.GetNames(AdapterSettings.Settings); + foreach (var name in names) + { + var fullName = $"{assemblyName}.{name}"; + result[name] = ConvertToEnvironmentName(fullName); + } + return result; + } + + /// + /// Converts the name to environment name. + /// + /// The name to convert. + /// The converted environment name. + private static string ConvertToEnvironmentName(string name) + { + return name.Replace(".", "_").ToUpper(); + } + } +} diff --git a/ricaun.RevitTest.TestAdapter/Resources/net48/ricaun.RevitTest.Console.zip b/ricaun.RevitTest.TestAdapter/Resources/net48/ricaun.RevitTest.Console.zip index 55793f3..d1ec630 100644 Binary files a/ricaun.RevitTest.TestAdapter/Resources/net48/ricaun.RevitTest.Console.zip and b/ricaun.RevitTest.TestAdapter/Resources/net48/ricaun.RevitTest.Console.zip differ diff --git a/ricaun.RevitTest.TestAdapter/Resources/net8.0-windows/ricaun.RevitTest.Console.zip b/ricaun.RevitTest.TestAdapter/Resources/net8.0-windows/ricaun.RevitTest.Console.zip index 52c6a11..5eaa589 100644 Binary files a/ricaun.RevitTest.TestAdapter/Resources/net8.0-windows/ricaun.RevitTest.Console.zip and b/ricaun.RevitTest.TestAdapter/Resources/net8.0-windows/ricaun.RevitTest.Console.zip differ diff --git a/ricaun.RevitTest.TestAdapter/Services/RevitTestConsole.cs b/ricaun.RevitTest.TestAdapter/Services/RevitTestConsole.cs index 86f85b1..b3adca4 100644 --- a/ricaun.RevitTest.TestAdapter/Services/RevitTestConsole.cs +++ b/ricaun.RevitTest.TestAdapter/Services/RevitTestConsole.cs @@ -15,11 +15,11 @@ private string GetEnvironmentVariable(string applicationPath) { try { - var enviromentVariable = Environment.GetEnvironmentVariable(applicationPath); - if (!string.IsNullOrEmpty(enviromentVariable)) + var environmentVariable = Environment.GetEnvironmentVariable(applicationPath); + if (!string.IsNullOrEmpty(environmentVariable)) { - AdapterLogger.Logger.Info($"Application Environment: {Path.GetFileName(enviromentVariable)}"); - return enviromentVariable; + AdapterLogger.Logger.Info($"Application Environment: {Path.GetFileName(environmentVariable)}"); + return environmentVariable; } } catch { } diff --git a/ricaun.RevitTest.TestAdapter/TestAdapter.cs b/ricaun.RevitTest.TestAdapter/TestAdapter.cs index b3ad238..b7f9f6a 100644 --- a/ricaun.RevitTest.TestAdapter/TestAdapter.cs +++ b/ricaun.RevitTest.TestAdapter/TestAdapter.cs @@ -5,6 +5,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; +using ricaun.RevitTest.TestAdapter.Metadatas; using ricaun.RevitTest.TestAdapter.Models; using ricaun.RevitTest.TestAdapter.Services; using System; @@ -40,6 +41,7 @@ protected TestAdapter() protected void Initialize(IDiscoveryContext discoveryContext, IMessageLogger messageLogger) { AdapterSettings.Create(discoveryContext); + EnvironmentSettings.Create(); AdapterLogger.Create(messageLogger, AdapterSettings.Settings.NUnit.Verbosity); AdapterLogger.Logger.InfoAny($"TestAdapter: {this.AdapterVersion}"); @@ -55,7 +57,7 @@ protected void Initialize(IDiscoveryContext discoveryContext, IMessageLogger mes AdapterLogger.Logger.DebugOnlyLocal($"\tTestAdapter: {this.AdapterVersion}"); AdapterLogger.Logger.DebugOnlyLocal($"\tAdapterSettings: {AdapterSettings.Settings}"); - var collection = Metadatas.XmlUtils.ParseKeyValues(discoveryContext.RunSettings.SettingsXml); + var collection = XmlUtils.ParseKeyValues(discoveryContext.RunSettings.SettingsXml); foreach (var item in collection) { AdapterLogger.Logger.DebugOnlyLocal($"\t{item.Key}: {item.Value}"); @@ -63,10 +65,20 @@ protected void Initialize(IDiscoveryContext discoveryContext, IMessageLogger mes AdapterLogger.Logger.DebugOnlyLocal("-"); - foreach (var item in Metadatas.MapperKey.GetNames(AdapterSettings.Instance)) + foreach (var item in MapperKey.GetNames(AdapterSettings.Instance)) { AdapterLogger.Logger.DebugOnlyLocal($"\t{item}"); } + + AdapterLogger.Logger.DebugOnlyLocal("-"); + + foreach (var item in EnvironmentSettings.GetEnvironmentNames()) + { + AdapterLogger.Logger.DebugOnlyLocal($"\tEnvironment: {item}"); + } + + //Environment.SetEnvironmentVariable("RICAUN_REVITTEST_TESTADAPTER_NUNIT_VERBOSITY", "3"); + AdapterLogger.Logger.DebugOnlyLocal("-DEBUG-"); //var RunSettings = Metadatas.Mapper.Map(collection, new RunSettingsModel()); diff --git a/ricaun.RevitTest.TestAdapter/TestDiscoverer.cs b/ricaun.RevitTest.TestAdapter/TestDiscoverer.cs index aeaddb0..342a059 100644 --- a/ricaun.RevitTest.TestAdapter/TestDiscoverer.cs +++ b/ricaun.RevitTest.TestAdapter/TestDiscoverer.cs @@ -2,6 +2,7 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Logging; using ricaun.RevitTest.TestAdapter.Extensions; +using ricaun.RevitTest.TestAdapter.Metadatas; using ricaun.RevitTest.TestAdapter.Services; using System; using System.Collections.Generic; @@ -44,7 +45,8 @@ internal static List GetTests( { foreach (var source in sources) { - Metadatas.MetadataSettings.Create(source); + MetadataSettings.Create(source); + EnvironmentSettings.Create(); AdapterLogger.Logger.Info($"DiscoverTests: {source}"); using (var revit = new RevitTestConsole(AdapterSettings.Settings.NUnit.Application, source)) diff --git a/ricaun.RevitTest.TestAdapter/TestExecutor.cs b/ricaun.RevitTest.TestAdapter/TestExecutor.cs index 5dc83d1..a9d8c65 100644 --- a/ricaun.RevitTest.TestAdapter/TestExecutor.cs +++ b/ricaun.RevitTest.TestAdapter/TestExecutor.cs @@ -1,10 +1,13 @@ using Microsoft.VisualStudio.TestPlatform.ObjectModel; using Microsoft.VisualStudio.TestPlatform.ObjectModel.Adapter; using ricaun.NUnit.Models; +using ricaun.RevitTest.TestAdapter.Extensions; +using ricaun.RevitTest.TestAdapter.Metadatas; using ricaun.RevitTest.TestAdapter.Services; using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; namespace ricaun.RevitTest.TestAdapter @@ -60,10 +63,44 @@ public void RunTests(IEnumerable sources, IRunContext runContext, IFrame task.GetAwaiter().GetResult(); } - public void Cancel() + #region Cancel + /// + /// Force to cancel `RunTests` if process exit + /// + private void InitializeCancel() + { + AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; + } + + private void FinishCancel() { + AppDomain.CurrentDomain.ProcessExit -= CurrentDomain_ProcessExit; } + private void CurrentDomain_ProcessExit(object sender, EventArgs e) + { + Cancel(); + } + + /// + /// Cancel force to kill process. + /// + public void Cancel() + { + FinishCancel(); + try + { + foreach (var process in ricaun.RevitTest.Command.Process.ProcessStart.GetProcesses()) + { + process.Kill(true); + } + } + catch (Exception ex) + { + System.IO.File.WriteAllText($"{this.GetType().Assembly.GetName().Name}.Exception.txt", ex.ToString()); + } + } + #endregion private static bool IsLogDebug => System.Diagnostics.Debugger.IsAttached || AdapterSettings.Settings.NUnit.Verbosity > 2; /// @@ -75,6 +112,8 @@ public void Cancel() /// private async Task RunTests(IFrameworkHandle frameworkHandle, string source, List tests = null) { + InitializeCancel(); + tests = tests ?? new List(); AdapterLogger.Logger.Info($"RunTest: {source} [TestCase: {tests.Count}]"); @@ -82,7 +121,8 @@ private async Task RunTests(IFrameworkHandle frameworkHandle, string source, Lis { AdapterLogger.Logger.Info($"Test: {test.FullyQualifiedName}.{test.DisplayName}"); } - Metadatas.MetadataSettings.Create(source); + MetadataSettings.Create(source); + EnvironmentSettings.Create(); AdapterLogger.Logger.Info("---------"); AdapterLogger.Logger.Info($"RevitTestConsole: {AdapterSettings.Settings.NUnit.Application}"); @@ -112,12 +152,14 @@ private async Task RunTests(IFrameworkHandle frameworkHandle, string source, Lis AdapterSettings.Settings.NUnit.Open, AdapterSettings.Settings.NUnit.Close, AdapterSettings.Settings.NUnit.Timeout, - AdapterLogger.Logger.Debug, (message) => { if (IsLogDebug) AdapterLogger.Logger.Debug(message); }, AdapterLogger.Logger.Warning, + AdapterLogger.Logger.Debug, (message) => { if (IsLogDebug) AdapterLogger.Logger.Debug(message); }, AdapterLogger.Logger.Warning, filters); } AdapterLogger.Logger.Info("---------"); + + FinishCancel(); } private static void RecordResultTestModel(IFrameworkHandle frameworkHandle, string source, List tests, TestModel testModel) @@ -131,6 +173,7 @@ private static void RecordResultTestModel(IFrameworkHandle frameworkHandle, stri AdapterLogger.Logger.Info($"\tTestCase: {testCase} [{testCase.DisplayName}] \t{testCase.Id}"); + testCase.LocalExtensionData = testModel; var testResult = new TestResult(testCase); testResult.Outcome = TestOutcome.Failed; diff --git a/ricaun.RevitTest.TestAdapter/ricaun.RevitTest.TestAdapter.csproj b/ricaun.RevitTest.TestAdapter/ricaun.RevitTest.TestAdapter.csproj index e153177..5c5d860 100644 --- a/ricaun.RevitTest.TestAdapter/ricaun.RevitTest.TestAdapter.csproj +++ b/ricaun.RevitTest.TestAdapter/ricaun.RevitTest.TestAdapter.csproj @@ -50,6 +50,12 @@ + + + NU1903 + + + @@ -156,5 +162,5 @@ - + diff --git a/ricaun.RevitTest.Tests/TestsRevitUI.cs b/ricaun.RevitTest.Tests/TestsRevitUI.cs new file mode 100644 index 0000000..6228767 --- /dev/null +++ b/ricaun.RevitTest.Tests/TestsRevitUI.cs @@ -0,0 +1,39 @@ +using Autodesk.Revit.ApplicationServices; +using Autodesk.Revit.UI; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace ricaun.RevitTest.Tests +{ + public class TestsRevitUI + { + static IEnumerable GetTypes() + { + yield return typeof(UIApplication); + yield return typeof(Application); + yield return typeof(UIFramework.RevitRibbonControl); + yield return typeof(Autodesk.Windows.RibbonControl); + } + [TestCaseSource(nameof(GetTypes))] + public void TestTypes(Type type) + { + Console.WriteLine(type); + } + +#if DEBUG + static IEnumerable GetAssemblyTypes() + { + return GetTypes().Select(t => t.Assembly); + } + + [TestCaseSource(nameof(GetAssemblyTypes))] + public void TestAssemblyTypes(Assembly assembly) + { + Console.WriteLine(assembly); + } +#endif + } +} \ No newline at end of file