Skip to content

Commit fb029cd

Browse files
Increase the test coverage
1 parent 0772b0c commit fb029cd

File tree

2 files changed

+336
-0
lines changed

2 files changed

+336
-0
lines changed
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
using FluentAssertions;
2+
3+
using Microsoft.Extensions.Logging;
4+
5+
using Moq;
6+
7+
using Nexus.Config;
8+
9+
using Xunit;
10+
11+
namespace Nexus.Config_unittests;
12+
13+
/// <summary>
14+
/// Unit tests for Settings class.
15+
/// </summary>
16+
public class SettingsTests : IDisposable
17+
{
18+
private Settings? m_Settings;
19+
20+
/// <summary>
21+
/// Disposes test resources.
22+
/// </summary>
23+
public void Dispose()
24+
{
25+
m_Settings?.Dispose();
26+
GC.SuppressFinalize(this);
27+
}
28+
29+
/// <summary>
30+
/// Verifies that constructor creates Settings instance.
31+
/// </summary>
32+
[Fact]
33+
public void Constructor_CreatesSettingsInstance()
34+
{
35+
// Act
36+
m_Settings = new Settings();
37+
38+
// Assert
39+
_ = m_Settings.Should().NotBeNull();
40+
}
41+
42+
/// <summary>
43+
/// Verifies that LoadConfiguration loads configuration from default path.
44+
/// </summary>
45+
[Fact]
46+
public void LoadConfiguration_WithNullPath_LoadsFromDefaultPath()
47+
{
48+
// Arrange
49+
m_Settings = new Settings();
50+
51+
// Act
52+
m_Settings.LoadConfiguration(null);
53+
54+
// Assert - should not throw
55+
_ = m_Settings.Should().NotBeNull();
56+
}
57+
58+
/// <summary>
59+
/// Verifies that LoadConfiguration loads configuration from custom path.
60+
/// </summary>
61+
[Fact]
62+
public void LoadConfiguration_WithCustomPath_LoadsFromCustomPath()
63+
{
64+
// Arrange
65+
m_Settings = new Settings();
66+
var customPath = AppContext.BaseDirectory;
67+
68+
// Act
69+
m_Settings.LoadConfiguration(customPath);
70+
71+
// Assert - should not throw
72+
_ = m_Settings.Should().NotBeNull();
73+
}
74+
75+
/// <summary>
76+
/// Verifies that Get returns configuration from default loader created in constructor.
77+
/// </summary>
78+
[Fact]
79+
public void Get_AfterConstructor_ReturnsConfiguration()
80+
{
81+
// Arrange
82+
m_Settings = new Settings();
83+
84+
// Act
85+
var config = m_Settings.Get();
86+
87+
// Assert - constructor creates ConfigurationLoader, so Get should work
88+
_ = config.Should().NotBeNull();
89+
_ = config.Should().BeOfType<Nexus.Config.Models.SharedConfiguration>();
90+
}
91+
92+
/// <summary>
93+
/// Verifies that Get returns configuration after LoadConfiguration.
94+
/// </summary>
95+
[Fact]
96+
public void Get_AfterLoadConfiguration_ReturnsConfiguration()
97+
{
98+
// Arrange
99+
m_Settings = new Settings();
100+
m_Settings.LoadConfiguration();
101+
102+
// Act
103+
var config = m_Settings.Get();
104+
105+
// Assert
106+
_ = config.Should().NotBeNull();
107+
_ = config.Should().BeOfType<Nexus.Config.Models.SharedConfiguration>();
108+
}
109+
110+
/// <summary>
111+
/// Verifies that Get caches configuration after first access.
112+
/// </summary>
113+
[Fact]
114+
public void Get_MultipleCalls_ReturnsSameCachedInstance()
115+
{
116+
// Arrange
117+
m_Settings = new Settings();
118+
m_Settings.LoadConfiguration();
119+
120+
// Act
121+
var config1 = m_Settings.Get();
122+
var config2 = m_Settings.Get();
123+
var config3 = m_Settings.Get();
124+
125+
// Assert - should return same instance (cached)
126+
_ = config1.Should().BeSameAs(config2);
127+
_ = config2.Should().BeSameAs(config3);
128+
}
129+
130+
/// <summary>
131+
/// Verifies that LoadConfiguration clears cached configuration.
132+
/// </summary>
133+
[Fact]
134+
public void LoadConfiguration_ClearsCachedConfiguration()
135+
{
136+
// Arrange
137+
m_Settings = new Settings();
138+
m_Settings.LoadConfiguration();
139+
140+
// Act
141+
m_Settings.LoadConfiguration();
142+
var config2 = m_Settings.Get();
143+
144+
// Assert - new configuration instance after reload
145+
_ = config2.Should().NotBeNull();
146+
147+
// Note: May or may not be same reference depending on configuration content
148+
}
149+
150+
/// <summary>
151+
/// Verifies that Get is thread-safe with concurrent access.
152+
/// </summary>
153+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
154+
[Fact]
155+
public async Task Get_WithConcurrentAccess_IsThreadSafe()
156+
{
157+
// Arrange
158+
m_Settings = new Settings();
159+
m_Settings.LoadConfiguration();
160+
const int threadCount = 10;
161+
var tasks = new Task<Nexus.Config.Models.SharedConfiguration>[threadCount];
162+
163+
// Act
164+
for (var i = 0; i < threadCount; i++)
165+
{
166+
tasks[i] = Task.Run(() => m_Settings!.Get());
167+
}
168+
169+
var results = await Task.WhenAll(tasks);
170+
171+
// Assert - all should return the same cached instance
172+
_ = results.Should().AllBeEquivalentTo(results[0], options => options.WithStrictOrdering());
173+
}
174+
175+
/// <summary>
176+
/// Verifies that ConfigureLogging configures logging with serviceMode false.
177+
/// </summary>
178+
[Fact]
179+
public void ConfigureLogging_WithServiceModeFalse_ConfiguresLogging()
180+
{
181+
// Arrange
182+
m_Settings = new Settings();
183+
m_Settings.LoadConfiguration();
184+
var mockLoggingBuilder = new Mock<ILoggingBuilder>();
185+
var isServiceMode = false;
186+
187+
// Act
188+
m_Settings.ConfigureLogging(mockLoggingBuilder.Object, isServiceMode);
189+
190+
// Assert - should not throw
191+
_ = m_Settings.Should().NotBeNull();
192+
}
193+
194+
/// <summary>
195+
/// Verifies that ConfigureLogging configures logging with serviceMode true.
196+
/// </summary>
197+
[Fact]
198+
public void ConfigureLogging_WithServiceModeTrue_ConfiguresLogging()
199+
{
200+
// Arrange
201+
m_Settings = new Settings();
202+
m_Settings.LoadConfiguration();
203+
var mockLoggingBuilder = new Mock<ILoggingBuilder>();
204+
var isServiceMode = true;
205+
206+
// Act
207+
m_Settings.ConfigureLogging(mockLoggingBuilder.Object, isServiceMode);
208+
209+
// Assert - should not throw
210+
_ = m_Settings.Should().NotBeNull();
211+
}
212+
213+
/// <summary>
214+
/// Verifies that ConfigureLogging works with default configuration from constructor.
215+
/// </summary>
216+
[Fact]
217+
public void ConfigureLogging_AfterConstructor_ConfiguresLogging()
218+
{
219+
// Arrange
220+
m_Settings = new Settings();
221+
var mockLoggingBuilder = new Mock<ILoggingBuilder>();
222+
var isServiceMode = false;
223+
224+
// Act - should work since constructor creates ConfigurationLoader
225+
m_Settings.ConfigureLogging(mockLoggingBuilder.Object, isServiceMode);
226+
227+
// Assert - should not throw
228+
_ = m_Settings.Should().NotBeNull();
229+
}
230+
231+
/// <summary>
232+
/// Verifies that Dispose can be called multiple times without throwing.
233+
/// </summary>
234+
[Fact]
235+
public void Dispose_CanBeCalledMultipleTimes()
236+
{
237+
// Arrange
238+
m_Settings = new Settings();
239+
240+
// Act & Assert - should not throw
241+
m_Settings.Dispose();
242+
m_Settings.Dispose();
243+
m_Settings.Dispose();
244+
}
245+
246+
/// <summary>
247+
/// Verifies that Dispose releases resources correctly.
248+
/// </summary>
249+
[Fact]
250+
public void Dispose_ReleasesResources()
251+
{
252+
// Arrange
253+
m_Settings = new Settings();
254+
m_Settings.LoadConfiguration();
255+
256+
// Act
257+
m_Settings.Dispose();
258+
259+
// Assert - should not throw
260+
_ = m_Settings.Should().NotBeNull();
261+
}
262+
263+
/// <summary>
264+
/// Verifies that operations after Dispose throw ObjectDisposedException.
265+
/// </summary>
266+
[Fact]
267+
public void Operations_AfterDispose_ThrowsObjectDisposedException()
268+
{
269+
// Arrange
270+
m_Settings = new Settings();
271+
m_Settings.LoadConfiguration();
272+
m_Settings.Dispose();
273+
274+
// Act & Assert - Get should throw ObjectDisposedException since lock is disposed
275+
_ = Assert.Throws<ObjectDisposedException>(() => m_Settings.Get());
276+
}
277+
278+
/// <summary>
279+
/// Verifies that LoadConfiguration with different paths creates different configuration loaders.
280+
/// </summary>
281+
[Fact]
282+
public void LoadConfiguration_WithDifferentPaths_CreatesNewLoader()
283+
{
284+
// Arrange
285+
m_Settings = new Settings();
286+
var path1 = AppContext.BaseDirectory;
287+
var path2 = AppContext.BaseDirectory; // Use same path to avoid directory not found
288+
289+
// Act
290+
m_Settings.LoadConfiguration(path1);
291+
var config1 = m_Settings.Get();
292+
m_Settings.LoadConfiguration(path2);
293+
var config2 = m_Settings.Get();
294+
295+
// Assert - should load successfully from both paths
296+
_ = config1.Should().NotBeNull();
297+
_ = config2.Should().NotBeNull();
298+
}
299+
300+
/// <summary>
301+
/// Verifies that Get handles race condition between read and write locks correctly.
302+
/// </summary>
303+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
304+
[Fact]
305+
public async Task Get_WithRaceCondition_HandlesCorrectly()
306+
{
307+
// Arrange
308+
m_Settings = new Settings();
309+
m_Settings.LoadConfiguration();
310+
311+
// Act - multiple threads accessing Get() simultaneously
312+
var tasks = new List<Task<Nexus.Config.Models.SharedConfiguration>>();
313+
for (var i = 0; i < 20; i++)
314+
{
315+
tasks.Add(Task.Run(() => m_Settings!.Get()));
316+
}
317+
318+
var results = await Task.WhenAll(tasks);
319+
320+
// Assert - all should return valid configuration
321+
_ = results.Should().AllSatisfy(c => c.Should().NotBeNull());
322+
323+
// All should be same instance (cached)
324+
var firstConfig = results[0];
325+
_ = results.Should().AllBeEquivalentTo(firstConfig, options => options.WithStrictOrdering());
326+
}
327+
}
328+

unittests/nexus_protocol_unittests/Services/EngineServiceTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,11 @@ public void Get_FromMultipleThreads_IsThreadSafe()
212212
{
213213
// Arrange - Initialize once before threads start
214214
EngineService.Initialize(m_FileSystem.Object, m_ProcessManager.Object, m_Settings.Object);
215+
215216
const int threadCount = 10;
216217
var engines = new List<IDebugEngine>();
217218
var exceptions = new List<Exception>();
219+
var barrier = new System.Threading.Barrier(threadCount + 1); // +1 for main thread
218220

219221
// Act
220222
var threads = new List<Thread>();
@@ -224,6 +226,9 @@ public void Get_FromMultipleThreads_IsThreadSafe()
224226
{
225227
try
226228
{
229+
// Wait for all threads to be ready
230+
barrier.SignalAndWait();
231+
227232
var engine = EngineService.Get();
228233
lock (engines)
229234
{
@@ -242,6 +247,9 @@ public void Get_FromMultipleThreads_IsThreadSafe()
242247
thread.Start();
243248
}
244249

250+
// Wait for all threads to be ready, then signal them to proceed
251+
barrier.SignalAndWait();
252+
245253
foreach (var thread in threads)
246254
{
247255
thread.Join();

0 commit comments

Comments
 (0)