Skip to content

Commit abba86d

Browse files
authored
Merge pull request #596 from LogExperts/unit_test_fix
fixing errors that happend in unit tests
2 parents e9ec6bf + 3eac110 commit abba86d

7 files changed

Lines changed: 80 additions & 40 deletions

File tree

src/PluginRegistry/PluginHashGenerator.Generated.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,35 @@ public static partial class PluginValidator
1010
{
1111
/// <summary>
1212
/// Gets pre-calculated SHA256 hashes for built-in plugins.
13-
/// Generated: 2026-06-03 11:38:48 UTC
13+
/// Generated: 2026-06-08 11:41:54 UTC
1414
/// Configuration: Release
1515
/// Plugin count: 21
1616
/// </summary>
1717
public static Dictionary<string, string> GetBuiltInPluginHashes()
1818
{
1919
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
2020
{
21-
["AutoColumnizer.dll"] = "B0965F04F73C61B0BC29208E9E652A9DB208E2E98C277F324C5E9FB51C2E8BD5",
21+
["AutoColumnizer.dll"] = "FAE9B82D5E6C5D84F9A9D605F0BD4907275B8606224E9FEA63384EDFEF37C6E2",
2222
["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6",
2323
["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6",
24-
["CsvColumnizer.dll"] = "CEC990903F1BD94965E37726A6025D8501C18C9932D96B111FA3FB9CDF71FCF7",
25-
["CsvColumnizer.dll (x86)"] = "CEC990903F1BD94965E37726A6025D8501C18C9932D96B111FA3FB9CDF71FCF7",
26-
["DefaultPlugins.dll"] = "B0766FFD6AA499BE4A854013B280A5DFF490B989EDB7406468D266A1DF892EBE",
27-
["FlashIconHighlighter.dll"] = "456544EA8F97FF53DD2203F99A00A0F7A669F355C1A6E9B0A6F5F44A498B52CA",
28-
["GlassfishColumnizer.dll"] = "64021C14C36807716462B9E9AC2B7AD5514061D74F90D08074D0BBAFF74BE3CD",
29-
["JsonColumnizer.dll"] = "F1D9F58572FC6795FCB855EE70A7F0717C291BFA0623B8D49B5ADB3381DEFC4B",
30-
["JsonCompactColumnizer.dll"] = "4A9CC547B1F05C073D66216F0A05D416D316D4A784D3DC7993AE127F8A83D916",
31-
["Log4jXmlColumnizer.dll"] = "04FD914B7587A5BF29B3D0DF13B0F939D50C9C78A1C3988B25FC5E6CE40302A6",
32-
["LogExpert.Resources.dll"] = "DF7378CA858BC60D5A781331D8CC6B79CF1026075D20202E6E7A384805351889",
24+
["CsvColumnizer.dll"] = "A32752B4A6A32D848714F76802CAD2C594111B9E82BB82321596BDF088742D35",
25+
["CsvColumnizer.dll (x86)"] = "A32752B4A6A32D848714F76802CAD2C594111B9E82BB82321596BDF088742D35",
26+
["DefaultPlugins.dll"] = "CE38F9B211EFC963BDF32435888AB9B57EAB67A4AF30BCD81B45CECC7F7A083E",
27+
["FlashIconHighlighter.dll"] = "5FCE162F24D638BA1FBBED873E730D91EE4369CABF2EB8622823D067F8807A58",
28+
["GlassfishColumnizer.dll"] = "230440A980B3CA824F4ED9CE15C790DE7D1E6A5347B5B560D18F553445FE9D87",
29+
["JsonColumnizer.dll"] = "52FDCE7A4527CAC3FF52638D6C71DCFB476BE3EA71DFF169844250E4A94630F7",
30+
["JsonCompactColumnizer.dll"] = "9A64B9CA107428C2AA4D11089A7E02F70CD2226001401574A21A5009B8E09F1F",
31+
["Log4jXmlColumnizer.dll"] = "BB21894E3E2E417970808C5489B2E105AD0093FC63CFA63D802C44F0CFCC07F8",
32+
["LogExpert.Resources.dll"] = "2B319CB107E22D7E1FCBCB553F9A5D19EBAF6CA1B9AE906BED8C5F46BCF31A46",
3333
["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93",
3434
["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93",
3535
["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D",
3636
["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D",
37-
["RegexColumnizer.dll"] = "36EB2C8B04313A4FE068CB7F69C057EF441471D12E636797092C87A63FF021A0",
38-
["SftpFileSystem.dll"] = "166BAA0702A9C0913CDA6F6B529601911BF145FDE7BB74A37C6CF086CAADB63C",
39-
["SftpFileSystem.dll (x86)"] = "B0ECE111B2C74EFD52181D070A757B1ACC3005B6C714FD231FAC73B26EFE9A90",
40-
["SftpFileSystem.Resources.dll"] = "BD2280AC54C20AC563000128080A2C9B7C8B6E689353E319FB7AE5371225FEFA",
41-
["SftpFileSystem.Resources.dll (x86)"] = "BD2280AC54C20AC563000128080A2C9B7C8B6E689353E319FB7AE5371225FEFA",
37+
["RegexColumnizer.dll"] = "AE31F0AD10FDEEB9AE403E57DAEDC320820F6C8FA653397A13DEEDE9C61CE796",
38+
["SftpFileSystem.dll"] = "01022F360A3D56E2E6C70BFDE496CF34B480C16067A174341929E49C23524C90",
39+
["SftpFileSystem.dll (x86)"] = "9740AE69A43EF9DB24132DFC1DC61540C6CF2438CC50800EAD3D80455D067E12",
40+
["SftpFileSystem.Resources.dll"] = "9753DC9D7191CD5EF9D65F206068FACE05F0BFE9A64272B3B8830ADDCE7D0F75",
41+
["SftpFileSystem.Resources.dll (x86)"] = "9753DC9D7191CD5EF9D65F206068FACE05F0BFE9A64272B3B8830ADDCE7D0F75",
4242

4343
};
4444
}

src/PluginRegistry/PluginRegistry.cs

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class PluginRegistry : IPluginRegistry
2828
#region Fields
2929

3030
private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
31-
private static PluginRegistry? _instance;
31+
private static volatile PluginRegistry? _instance;
3232
private static readonly Lock _lock = new();
3333

3434
private readonly IFileSystemCallback _fileSystemCallback = new FileSystemCallback();
@@ -101,14 +101,25 @@ public static PluginRegistry Create (string applicationConfigurationFolder, int
101101

102102
lock (_lock)
103103
{
104-
_instance = new PluginRegistry(applicationConfigurationFolder, pollingInterval);
105-
}
104+
// Re-check inside the lock: another thread may have created the
105+
// instance while this one was waiting on the lock.
106+
// Happens mostly in UnitTests where Create() is called multiple times.
107+
// CA1508 is not applicable here because the null check is intentional for initialization.
108+
#pragma warning disable CA1508 // Avoid dead conditional code
109+
if (_instance == null)
110+
{
111+
var registry = new PluginRegistry(applicationConfigurationFolder, pollingInterval);
106112

107-
_applicationConfigurationFolder = applicationConfigurationFolder;
108-
PollingInterval = pollingInterval;
113+
// Fully initialize before publishing so the lock-free fast path
114+
// above never returns a half-loaded registry.
115+
registry.LoadPlugins();
116+
117+
_instance = registry;
118+
}
119+
#pragma warning restore CA1508 // Avoid dead conditional code
120+
}
109121

110-
_instance.LoadPlugins();
111-
return Instance;
122+
return _instance;
112123
}
113124

114125
/// <summary>

src/RegexColumnizer.UnitTests/RegexColumnizerErrorHandlingTests.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.Versioning;
2+
using System.Windows.Forms;
23

34
using ColumnizerLib;
45

@@ -127,12 +128,19 @@ public void LoadConfig_CorruptJsonFile_DisplaysErrorAndUsesDefaults ()
127128
string jsonPath = Path.Join(_testDirectory, "Regex1Columnizer.json");
128129
File.WriteAllText(jsonPath, "{ corrupt json content }");
129130

130-
var columnizer = new Regex1Columnizer();
131+
var errors = new List<(string Message, string Title, MessageBoxIcon Icon)>();
132+
var columnizer = new Regex1Columnizer
133+
{
134+
ShowError = (message, title, icon) => errors.Add((message, title, icon))
135+
};
131136

132137
// Act - Should not throw, should handle gracefully
133138
Assert.DoesNotThrow(() => columnizer.LoadConfig(_testDirectory));
134139

135-
// Assert - Should fall back to defaults
140+
// Assert - error was reported (dialog would have shown in release)
141+
Assert.That(errors, Has.Count.EqualTo(1));
142+
143+
// Assert - and should have fallen back to defaults
136144
Assert.That(columnizer.GetName(), Is.EqualTo("Regex1"));
137145
Assert.That(columnizer.GetColumnCount(), Is.GreaterThan(0));
138146
}
@@ -173,12 +181,19 @@ public void LoadConfig_CorruptXmlFile_DisplaysErrorAndUsesDefaults ()
173181
string xmlPath = Path.Join(_testDirectory, "Regex1Columnizer.xml");
174182
File.WriteAllText(xmlPath, "<InvalidXml>No closing tag");
175183

176-
var columnizer = new Regex1Columnizer();
184+
var errors = new List<(string Message, string Title, MessageBoxIcon Icon)>();
185+
var columnizer = new Regex1Columnizer
186+
{
187+
ShowError = (message, title, icon) => errors.Add((message, title, icon))
188+
};
177189

178190
// Act - Should not throw
179191
Assert.DoesNotThrow(() => columnizer.LoadConfig(_testDirectory));
180192

181-
// Assert - Should fall back to defaults
193+
// Assert - error was reported (dialog would have shown in release)
194+
Assert.That(errors, Has.Count.EqualTo(1));
195+
196+
// Assert - and should have fallen back to defaults
182197
Assert.That(columnizer.GetName(), Is.EqualTo("Regex1"));
183198
}
184199

src/RegexColumnizer.UnitTests/RegexColumnizerLoadConfigTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Runtime.Versioning;
2+
using System.Windows.Forms;
23

34
using NUnit.Framework;
45

@@ -119,12 +120,19 @@ public void LoadConfig_CorruptJsonFile_FallsBackToDefault ()
119120
string jsonPath = Path.Join(_testDirectory, "Regex1Columnizer.json");
120121
File.WriteAllText(jsonPath, "{ this is not valid json }");
121122

122-
var columnizer = new Regex1Columnizer();
123+
var errors = new List<(string Message, string Title, MessageBoxIcon Icon)>();
124+
var columnizer = new Regex1Columnizer
125+
{
126+
ShowError = (message, title, icon) => errors.Add((message, title, icon))
127+
};
123128

124129
// Act - Should not throw, should fall back to defaults
125130
Assert.DoesNotThrow(() => columnizer.LoadConfig(_testDirectory));
126131

127-
// Assert
132+
// Assert - error was reported (dialog would have shown in release)
133+
Assert.That(errors, Has.Count.EqualTo(1));
134+
135+
// Assert - and should have fallen back to defaults
128136
Assert.That(columnizer.GetName(), Is.EqualTo("Regex1"));
129137
}
130138

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
using System.Runtime.CompilerServices;
2+
3+
[assembly: InternalsVisibleTo("LogExpert.RegexColumnizer.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100619e9beea345a3bb5e15f55b29ddf40d96e9bb473ae58304fc63dfb3e9c94d8944bb7e45324ee0bef3e345dccba79b0bf64b85a128a7f261861899add639218ddaeb2acc6fcc746d6acb5bb212d375a0967756af192cfdb6cf0bff666a0fe535600abda860d3eafaff4ef1c9b5710181f72d996ca9c29ed64bae4a5fd916dea5")]

src/RegexColumnizer/RegexColumnizer.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public abstract class BaseRegexColumnizer : ILogLineMemoryColumnizer, IColumnize
2828
private readonly XmlSerializer _xml = new(typeof(RegexColumnizerConfig));
2929
private string[] _columns;
3030
private RegexColumnizerConfig _config;
31+
32+
// Seam for error notification.
33+
// Defaults to a modal dialog, in tests replaced so they can assert without blocking on a headless runner.
34+
internal Action<string, string, MessageBoxIcon> ShowError { get; set; } = static (message, title, icon) => MessageBox.Show(message, title, MessageBoxButtons.OK, icon);
3135
#endregion
3236

3337
#region Properties
@@ -233,7 +237,7 @@ ArgumentNullException or
233237
FileNotFoundException or
234238
DirectoryNotFoundException)
235239
{
236-
_ = MessageBox.Show(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize);
240+
ShowError(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize, MessageBoxIcon.Error);
237241
_config = new RegexColumnizerConfig
238242
{
239243
Name = GetName()
@@ -252,7 +256,7 @@ FileNotFoundException or
252256
}
253257
catch (JsonException ex)
254258
{
255-
_ = MessageBox.Show(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize);
259+
ShowError(ex.Message, Resources.RegexColumnizer_UI_Title_Deserialize, MessageBoxIcon.Error);
256260
_config = new RegexColumnizerConfig
257261
{
258262
Name = GetName()
@@ -424,10 +428,9 @@ public void Configure (ILogLineMemoryColumnizerCallback callback, string configD
424428
catch (Exception ex) when (ex is IOException or
425429
UnauthorizedAccessException)
426430
{
427-
_ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToCreateConfigurationDirectory, ex.Message),
428-
Resources.RegexColumnizer_UI_Title_Error,
429-
MessageBoxButtons.OK,
430-
MessageBoxIcon.Error);
431+
ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToCreateConfigurationDirectory, ex.Message),
432+
Resources.RegexColumnizer_UI_Title_Error,
433+
MessageBoxIcon.Error);
431434
return;
432435
}
433436
}
@@ -460,15 +463,15 @@ public void Configure (ILogLineMemoryColumnizerCallback callback, string configD
460463
}
461464
catch (RegexMatchTimeoutException ex)
462465
{
463-
_ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_RegexTimeout, ex.Message), Resources.RegexColumnizer_UI_Title_Warning, MessageBoxButtons.OK, MessageBoxIcon.Warning);
466+
ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_RegexTimeout, ex.Message), Resources.RegexColumnizer_UI_Title_Warning, MessageBoxIcon.Error);
464467
}
465468
catch (ArgumentException ex)
466469
{
467-
_ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_InvalidRegexPattern, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
470+
ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_InvalidRegexPattern, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxIcon.Error);
468471
}
469472
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
470473
{
471-
_ = MessageBox.Show(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToSaveConfiguration, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
474+
ShowError(string.Format(CultureInfo.InvariantCulture, Resources.RegexColumnizer_UI_Message_FailedToSaveConfiguration, ex.Message), Resources.RegexColumnizer_UI_Title_Error, MessageBoxIcon.Error);
472475
}
473476
}
474477
}

src/RegexColumnizer/RegexColumnizer.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
2828
<CustomToolNamespace></CustomToolNamespace>
2929
</EmbeddedResource>
30-
30+
3131
<EmbeddedResource Update="Resources.de.resx">
3232
<DependentUpon>Resources.resx</DependentUpon>
3333
</EmbeddedResource>
@@ -41,7 +41,7 @@
4141
</Compile>
4242
</ItemGroup>
4343

44-
<ItemGroup>
44+
<ItemGroup>
4545
<None Update="RegexColumnizer.manifest.json">
4646
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
4747
</None>

0 commit comments

Comments
 (0)