Skip to content

Commit 2148107

Browse files
committed
test(http-tests): pin multicast isolation and InternalError fault tolerance
1 parent deceaef commit 2148107

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

tests/MTConnect.NET-HTTP-Tests/Clients/HttpClientDeviceModel.cs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,5 +326,85 @@ public void DeviceReceivedHandlerThrowingDoesNotBreakCachePopulation()
326326
client.Stop();
327327
}
328328
}
329+
330+
/// <summary>Pins the behaviour expressed by the test name: device received fires for all subscribers when one throws.</summary>
331+
[Test]
332+
public void DeviceReceivedFiresForAllSubscribersWhenOneThrows()
333+
{
334+
// Wire two subscribers: a throwing one FIRST, a recording one SECOND.
335+
// The recording subscriber must still receive every device because
336+
// RaiseDeviceReceived iterates the invocation list with per-delegate
337+
// try/catch, not a single try/catch over the multicast.
338+
var client = new MTConnectHttpClient(Hostname, Port);
339+
340+
client.DeviceReceived += (_, _) => throw new InvalidOperationException("first handler throws");
341+
342+
var recorded = new List<IDevice>();
343+
var recordedLock = new object();
344+
client.DeviceReceived += (_, device) =>
345+
{
346+
lock (recordedLock) { recorded.Add(device); }
347+
};
348+
349+
using var probeSignal = new ManualResetEventSlim(false);
350+
client.ProbeReceived += (_, _) => probeSignal.Set();
351+
352+
try
353+
{
354+
client.Start();
355+
356+
Assert.That(probeSignal.Wait(ProbeWaitTimeoutMs), Is.True, "ProbeReceived did not fire within the timeout");
357+
358+
List<IDevice> snapshot;
359+
lock (recordedLock) { snapshot = new List<IDevice>(recorded); }
360+
361+
Assert.That(snapshot.Count, Is.EqualTo(ExpectedDocumentEntryCount),
362+
"subscribers after a throwing one must still be invoked");
363+
}
364+
finally
365+
{
366+
client.Stop();
367+
}
368+
}
369+
370+
/// <summary>Pins the behaviour expressed by the test name: internal error handler throwing does not break device received fan out.</summary>
371+
[Test]
372+
public void InternalErrorHandlerThrowingDoesNotBreakDeviceReceivedFanOut()
373+
{
374+
// The fix routes swallowed DeviceReceived faults through InternalError.
375+
// If InternalError ITSELF throws, that secondary fault must also be
376+
// swallowed so the rest of the DeviceReceived fan-out continues.
377+
var client = new MTConnectHttpClient(Hostname, Port);
378+
379+
client.InternalError += (_, _) => throw new InvalidOperationException("InternalError throws");
380+
client.DeviceReceived += (_, _) => throw new InvalidOperationException("first DeviceReceived throws");
381+
382+
var recorded = new List<IDevice>();
383+
var recordedLock = new object();
384+
client.DeviceReceived += (_, device) =>
385+
{
386+
lock (recordedLock) { recorded.Add(device); }
387+
};
388+
389+
using var probeSignal = new ManualResetEventSlim(false);
390+
client.ProbeReceived += (_, _) => probeSignal.Set();
391+
392+
try
393+
{
394+
client.Start();
395+
396+
Assert.That(probeSignal.Wait(ProbeWaitTimeoutMs), Is.True, "ProbeReceived did not fire within the timeout");
397+
398+
List<IDevice> snapshot;
399+
lock (recordedLock) { snapshot = new List<IDevice>(recorded); }
400+
401+
Assert.That(snapshot.Count, Is.EqualTo(ExpectedDocumentEntryCount),
402+
"InternalError throwing must not break the DeviceReceived fan-out");
403+
}
404+
finally
405+
{
406+
client.Stop();
407+
}
408+
}
329409
}
330410
}

0 commit comments

Comments
 (0)