From 46d74b91e9af5ef7a1e2d3c6a882f72bf6023e84 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Mon, 8 Jun 2026 13:21:49 +0200 Subject: [PATCH 1/2] fixing errors that happend in unit tests updated unittests to not expect user interaction throw modal dialogs --- src/PluginRegistry/PluginRegistry.cs | 25 +++++++++++++------ .../RegexColumnizerErrorHandlingTests.cs | 23 ++++++++++++++--- .../RegexColumnizerLoadConfigTests.cs | 12 +++++++-- src/RegexColumnizer/AssemblyInfo.cs | 3 +++ src/RegexColumnizer/RegexColumnizer.cs | 21 +++++++++------- src/RegexColumnizer/RegexColumnizer.csproj | 4 +-- 6 files changed, 64 insertions(+), 24 deletions(-) create mode 100644 src/RegexColumnizer/AssemblyInfo.cs diff --git a/src/PluginRegistry/PluginRegistry.cs b/src/PluginRegistry/PluginRegistry.cs index 3eb28c49..431c75fe 100644 --- a/src/PluginRegistry/PluginRegistry.cs +++ b/src/PluginRegistry/PluginRegistry.cs @@ -28,7 +28,7 @@ public class PluginRegistry : IPluginRegistry #region Fields private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - private static PluginRegistry? _instance; + private static volatile PluginRegistry? _instance; private static readonly Lock _lock = new(); private readonly IFileSystemCallback _fileSystemCallback = new FileSystemCallback(); @@ -101,14 +101,25 @@ public static PluginRegistry Create (string applicationConfigurationFolder, int lock (_lock) { - _instance = new PluginRegistry(applicationConfigurationFolder, pollingInterval); - } + // Re-check inside the lock: another thread may have created the + // instance while this one was waiting on the lock. + // Happens mostly in UnitTests where Create() is called multiple times. + // CA1508 is not applicable here because the null check is intentional for initialization. +#pragma warning disable CA1508 // Avoid dead conditional code + if (_instance == null) + { + var registry = new PluginRegistry(applicationConfigurationFolder, pollingInterval); - _applicationConfigurationFolder = applicationConfigurationFolder; - PollingInterval = pollingInterval; + // Fully initialize before publishing so the lock-free fast path + // above never returns a half-loaded registry. + registry.LoadPlugins(); + + _instance = registry; + } +#pragma warning restore CA1508 // Avoid dead conditional code + } - _instance.LoadPlugins(); - return Instance; + return _instance; } /// diff --git a/src/RegexColumnizer.UnitTests/RegexColumnizerErrorHandlingTests.cs b/src/RegexColumnizer.UnitTests/RegexColumnizerErrorHandlingTests.cs index c2d4ff30..83f51fcb 100644 --- a/src/RegexColumnizer.UnitTests/RegexColumnizerErrorHandlingTests.cs +++ b/src/RegexColumnizer.UnitTests/RegexColumnizerErrorHandlingTests.cs @@ -1,4 +1,5 @@ using System.Runtime.Versioning; +using System.Windows.Forms; using ColumnizerLib; @@ -127,12 +128,19 @@ public void LoadConfig_CorruptJsonFile_DisplaysErrorAndUsesDefaults () string jsonPath = Path.Join(_testDirectory, "Regex1Columnizer.json"); File.WriteAllText(jsonPath, "{ corrupt json content }"); - var columnizer = new Regex1Columnizer(); + var errors = new List<(string Message, string Title, MessageBoxIcon Icon)>(); + var columnizer = new Regex1Columnizer + { + ShowError = (message, title, icon) => errors.Add((message, title, icon)) + }; // Act - Should not throw, should handle gracefully Assert.DoesNotThrow(() => columnizer.LoadConfig(_testDirectory)); - // Assert - Should fall back to defaults + // Assert - error was reported (dialog would have shown in release) + Assert.That(errors, Has.Count.EqualTo(1)); + + // Assert - and should have fallen back to defaults Assert.That(columnizer.GetName(), Is.EqualTo("Regex1")); Assert.That(columnizer.GetColumnCount(), Is.GreaterThan(0)); } @@ -173,12 +181,19 @@ public void LoadConfig_CorruptXmlFile_DisplaysErrorAndUsesDefaults () string xmlPath = Path.Join(_testDirectory, "Regex1Columnizer.xml"); File.WriteAllText(xmlPath, "No closing tag"); - var columnizer = new Regex1Columnizer(); + var errors = new List<(string Message, string Title, MessageBoxIcon Icon)>(); + var columnizer = new Regex1Columnizer + { + ShowError = (message, title, icon) => errors.Add((message, title, icon)) + }; // Act - Should not throw Assert.DoesNotThrow(() => columnizer.LoadConfig(_testDirectory)); - // Assert - Should fall back to defaults + // Assert - error was reported (dialog would have shown in release) + Assert.That(errors, Has.Count.EqualTo(1)); + + // Assert - and should have fallen back to defaults Assert.That(columnizer.GetName(), Is.EqualTo("Regex1")); } diff --git a/src/RegexColumnizer.UnitTests/RegexColumnizerLoadConfigTests.cs b/src/RegexColumnizer.UnitTests/RegexColumnizerLoadConfigTests.cs index 70d5d0c0..eecf40c1 100644 --- a/src/RegexColumnizer.UnitTests/RegexColumnizerLoadConfigTests.cs +++ b/src/RegexColumnizer.UnitTests/RegexColumnizerLoadConfigTests.cs @@ -1,4 +1,5 @@ using System.Runtime.Versioning; +using System.Windows.Forms; using NUnit.Framework; @@ -119,12 +120,19 @@ public void LoadConfig_CorruptJsonFile_FallsBackToDefault () string jsonPath = Path.Join(_testDirectory, "Regex1Columnizer.json"); File.WriteAllText(jsonPath, "{ this is not valid json }"); - var columnizer = new Regex1Columnizer(); + var errors = new List<(string Message, string Title, MessageBoxIcon Icon)>(); + var columnizer = new Regex1Columnizer + { + ShowError = (message, title, icon) => errors.Add((message, title, icon)) + }; // Act - Should not throw, should fall back to defaults Assert.DoesNotThrow(() => columnizer.LoadConfig(_testDirectory)); - // Assert + // Assert - error was reported (dialog would have shown in release) + Assert.That(errors, Has.Count.EqualTo(1)); + + // Assert - and should have fallen back to defaults Assert.That(columnizer.GetName(), Is.EqualTo("Regex1")); } diff --git a/src/RegexColumnizer/AssemblyInfo.cs b/src/RegexColumnizer/AssemblyInfo.cs new file mode 100644 index 00000000..4d520926 --- /dev/null +++ b/src/RegexColumnizer/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("LogExpert.RegexColumnizer.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100619e9beea345a3bb5e15f55b29ddf40d96e9bb473ae58304fc63dfb3e9c94d8944bb7e45324ee0bef3e345dccba79b0bf64b85a128a7f261861899add639218ddaeb2acc6fcc746d6acb5bb212d375a0967756af192cfdb6cf0bff666a0fe535600abda860d3eafaff4ef1c9b5710181f72d996ca9c29ed64bae4a5fd916dea5")] \ No newline at end of file diff --git a/src/RegexColumnizer/RegexColumnizer.cs b/src/RegexColumnizer/RegexColumnizer.cs index b13cae87..f31ae313 100644 --- a/src/RegexColumnizer/RegexColumnizer.cs +++ b/src/RegexColumnizer/RegexColumnizer.cs @@ -28,6 +28,10 @@ public abstract class BaseRegexColumnizer : ILogLineMemoryColumnizer, IColumnize private readonly XmlSerializer _xml = new(typeof(RegexColumnizerConfig)); private string[] _columns; private RegexColumnizerConfig _config; + + // Seam for error notification. + // Defaults to a modal dialog, in tests replaced so they can assert without blocking on a headless runner. + internal Action ShowError { get; set; } = static (message, title, icon) => MessageBox.Show(message, title, MessageBoxButtons.OK, icon); #endregion #region Properties @@ -233,7 +237,7 @@ ArgumentNullException or FileNotFoundException or DirectoryNotFoundException) { - _ = MessageBox.Show(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize); + ShowError(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize, MessageBoxIcon.Error); _config = new RegexColumnizerConfig { Name = GetName() @@ -252,7 +256,7 @@ FileNotFoundException or } catch (JsonException ex) { - _ = MessageBox.Show(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize); + ShowError(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize, MessageBoxIcon.Error); _config = new RegexColumnizerConfig { Name = GetName() @@ -424,10 +428,9 @@ public void Configure (ILogLineMemoryColumnizerCallback callback, string configD catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { - _ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToCreateConfigurationDirectory, ex.Message), - Resources.RegexColumnizer_UI_Title_Error, - MessageBoxButtons.OK, - MessageBoxIcon.Error); + ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToCreateConfigurationDirectory, ex.Message), + Resources.RegexColumnizer_UI_Title_Error, + MessageBoxIcon.Error); return; } } @@ -460,15 +463,15 @@ public void Configure (ILogLineMemoryColumnizerCallback callback, string configD } catch (RegexMatchTimeoutException ex) { - _ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_RegexTimeout, ex.Message), Resources.RegexColumnizer_UI_Title_Warning, MessageBoxButtons.OK, MessageBoxIcon.Warning); + ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_RegexTimeout, ex.Message), Resources.RegexColumnizer_UI_Title_Warning, MessageBoxIcon.Error); } catch (ArgumentException ex) { - _ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_InvalidRegexPattern, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_InvalidRegexPattern, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxIcon.Error); } catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) { - _ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToSaveConfiguration, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxButtons.OK, MessageBoxIcon.Error); + ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToSaveConfiguration, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxIcon.Error); } } } diff --git a/src/RegexColumnizer/RegexColumnizer.csproj b/src/RegexColumnizer/RegexColumnizer.csproj index 6797cf88..dcffd8ba 100644 --- a/src/RegexColumnizer/RegexColumnizer.csproj +++ b/src/RegexColumnizer/RegexColumnizer.csproj @@ -27,7 +27,7 @@ Resources.Designer.cs - + Resources.resx @@ -41,7 +41,7 @@ - + PreserveNewest From 3eac110ba018ad6c92cfde84acae1322e5e9e1fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 8 Jun 2026 11:41:55 +0000 Subject: [PATCH 2/2] chore: update plugin hashes [skip ci] --- .../PluginHashGenerator.Generated.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index 21fff2a6..12fde81c 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-06-03 11:38:48 UTC + /// Generated: 2026-06-08 11:41:54 UTC /// Configuration: Release /// Plugin count: 21 /// @@ -18,27 +18,27 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "B0965F04F73C61B0BC29208E9E652A9DB208E2E98C277F324C5E9FB51C2E8BD5", + ["AutoColumnizer.dll"] = "FAE9B82D5E6C5D84F9A9D605F0BD4907275B8606224E9FEA63384EDFEF37C6E2", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "CEC990903F1BD94965E37726A6025D8501C18C9932D96B111FA3FB9CDF71FCF7", - ["CsvColumnizer.dll (x86)"] = "CEC990903F1BD94965E37726A6025D8501C18C9932D96B111FA3FB9CDF71FCF7", - ["DefaultPlugins.dll"] = "B0766FFD6AA499BE4A854013B280A5DFF490B989EDB7406468D266A1DF892EBE", - ["FlashIconHighlighter.dll"] = "456544EA8F97FF53DD2203F99A00A0F7A669F355C1A6E9B0A6F5F44A498B52CA", - ["GlassfishColumnizer.dll"] = "64021C14C36807716462B9E9AC2B7AD5514061D74F90D08074D0BBAFF74BE3CD", - ["JsonColumnizer.dll"] = "F1D9F58572FC6795FCB855EE70A7F0717C291BFA0623B8D49B5ADB3381DEFC4B", - ["JsonCompactColumnizer.dll"] = "4A9CC547B1F05C073D66216F0A05D416D316D4A784D3DC7993AE127F8A83D916", - ["Log4jXmlColumnizer.dll"] = "04FD914B7587A5BF29B3D0DF13B0F939D50C9C78A1C3988B25FC5E6CE40302A6", - ["LogExpert.Resources.dll"] = "DF7378CA858BC60D5A781331D8CC6B79CF1026075D20202E6E7A384805351889", + ["CsvColumnizer.dll"] = "A32752B4A6A32D848714F76802CAD2C594111B9E82BB82321596BDF088742D35", + ["CsvColumnizer.dll (x86)"] = "A32752B4A6A32D848714F76802CAD2C594111B9E82BB82321596BDF088742D35", + ["DefaultPlugins.dll"] = "CE38F9B211EFC963BDF32435888AB9B57EAB67A4AF30BCD81B45CECC7F7A083E", + ["FlashIconHighlighter.dll"] = "5FCE162F24D638BA1FBBED873E730D91EE4369CABF2EB8622823D067F8807A58", + ["GlassfishColumnizer.dll"] = "230440A980B3CA824F4ED9CE15C790DE7D1E6A5347B5B560D18F553445FE9D87", + ["JsonColumnizer.dll"] = "52FDCE7A4527CAC3FF52638D6C71DCFB476BE3EA71DFF169844250E4A94630F7", + ["JsonCompactColumnizer.dll"] = "9A64B9CA107428C2AA4D11089A7E02F70CD2226001401574A21A5009B8E09F1F", + ["Log4jXmlColumnizer.dll"] = "BB21894E3E2E417970808C5489B2E105AD0093FC63CFA63D802C44F0CFCC07F8", + ["LogExpert.Resources.dll"] = "2B319CB107E22D7E1FCBCB553F9A5D19EBAF6CA1B9AE906BED8C5F46BCF31A46", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "36EB2C8B04313A4FE068CB7F69C057EF441471D12E636797092C87A63FF021A0", - ["SftpFileSystem.dll"] = "166BAA0702A9C0913CDA6F6B529601911BF145FDE7BB74A37C6CF086CAADB63C", - ["SftpFileSystem.dll (x86)"] = "B0ECE111B2C74EFD52181D070A757B1ACC3005B6C714FD231FAC73B26EFE9A90", - ["SftpFileSystem.Resources.dll"] = "BD2280AC54C20AC563000128080A2C9B7C8B6E689353E319FB7AE5371225FEFA", - ["SftpFileSystem.Resources.dll (x86)"] = "BD2280AC54C20AC563000128080A2C9B7C8B6E689353E319FB7AE5371225FEFA", + ["RegexColumnizer.dll"] = "AE31F0AD10FDEEB9AE403E57DAEDC320820F6C8FA653397A13DEEDE9C61CE796", + ["SftpFileSystem.dll"] = "01022F360A3D56E2E6C70BFDE496CF34B480C16067A174341929E49C23524C90", + ["SftpFileSystem.dll (x86)"] = "9740AE69A43EF9DB24132DFC1DC61540C6CF2438CC50800EAD3D80455D067E12", + ["SftpFileSystem.Resources.dll"] = "9753DC9D7191CD5EF9D65F206068FACE05F0BFE9A64272B3B8830ADDCE7D0F75", + ["SftpFileSystem.Resources.dll (x86)"] = "9753DC9D7191CD5EF9D65F206068FACE05F0BFE9A64272B3B8830ADDCE7D0F75", }; }