diff --git a/src/JSSoft.Communication/Grpc/AdaptorClient.cs b/src/JSSoft.Communication/Grpc/AdaptorClient.cs
index 6cf7899..9de71e1 100644
--- a/src/JSSoft.Communication/Grpc/AdaptorClient.cs
+++ b/src/JSSoft.Communication/Grpc/AdaptorClient.cs
@@ -403,23 +403,31 @@ private void InvokeCallback(IService service, string name, string[] data)
}
var methodDescriptors = _methodsByService[service];
- if (methodDescriptors.Contains(name) != true)
+ if (methodDescriptors.Contains(name) == true)
{
- throw new InvalidOperationException("Invalid method name.");
+ var methodDescriptor = methodDescriptors[name];
+ var args = _serializer!.DeserializeMany(methodDescriptor.ParameterTypes, data);
+ var instance = _descriptor!.ClientInstances[service];
+ Task.Run(() => methodDescriptor.InvokeAsync(_serviceContext, instance, args));
+ }
+ else
+ {
+ LogUtility.Warn($"Method '{name}' is not found.");
}
-
- var methodDescriptor = methodDescriptors[name];
- var args = _serializer!.DeserializeMany(methodDescriptor.ParameterTypes, data);
- var instance = _descriptor!.ClientInstances[service];
- Task.Run(() => methodDescriptor.InvokeAsync(_serviceContext, instance, args));
}
private void InvokeCallback(PollReply reply)
{
foreach (var item in reply.Items)
{
- var service = _serviceByName[item.ServiceName];
- InvokeCallback(service, item.Name, [.. item.Data]);
+ if (_serviceByName.TryGetValue(item.ServiceName, out var service) == true)
+ {
+ InvokeCallback(service, item.Name, [.. item.Data]);
+ }
+ else
+ {
+ LogUtility.Warn($"Service '{item.ServiceName}' is not found.");
+ }
}
reply.Items.Clear();
diff --git a/test/JSSoft.Communication.Tests/CallbackNoneTest.cs b/test/JSSoft.Communication.Tests/CallbackNoneTest.cs
new file mode 100644
index 0000000..7e63738
--- /dev/null
+++ b/test/JSSoft.Communication.Tests/CallbackNoneTest.cs
@@ -0,0 +1,83 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+using JSSoft.Communication.Tests.Extensions;
+using Xunit.Abstractions;
+
+namespace JSSoft.Communication.Tests;
+
+public class CallbackNoneTest : IAsyncLifetime
+{
+ private const int Timeout = 3000;
+ private readonly ITestOutputHelper _logger;
+ private readonly TestServer1 _testServer = new();
+ private readonly ServerContext _serverContext;
+ private readonly ClientContext _clientContext;
+ private readonly RandomEndPoint _endPoint = new();
+ private ITestService1? _server;
+
+ private Guid _clientToken;
+ private Guid _serverToken;
+
+ public CallbackNoneTest(ITestOutputHelper logger)
+ {
+ _logger = logger;
+ _serverContext = new(_testServer) { EndPoint = _endPoint };
+ _clientContext = new(new ClientService()) { EndPoint = _endPoint };
+ logger.WriteLine($"{_endPoint}");
+ }
+
+ public interface ITestService1
+ {
+ void Invoke();
+ }
+
+ public interface ITestService2
+ {
+ void Invoke();
+ }
+
+ public interface ITestCallback2
+ {
+ void OnInvoked();
+ }
+
+ [Fact]
+ public void Callback1_Test()
+ {
+ var manualResetEvent = new ManualResetEvent(false);
+ _clientContext.Disconnected += ClientContext_Disconnected;
+ _server!.Invoke();
+ Assert.False(manualResetEvent.WaitOne(Timeout));
+
+ void ClientContext_Disconnected(object? sender, EventArgs e)
+ {
+ manualResetEvent.Set();
+ }
+ }
+
+ public async Task InitializeAsync()
+ {
+ _serverToken = await _serverContext.OpenAsync(CancellationToken.None);
+ _logger.WriteLine($"Server is opened: {_serverToken}");
+ _clientToken = await _clientContext.OpenAsync(CancellationToken.None);
+ _logger.WriteLine($"Client is opened: {_clientToken}");
+ _server = _testServer;
+ }
+
+ public async Task DisposeAsync()
+ {
+ await _serverContext.ReleaseAsync(_serverToken);
+ _logger.WriteLine($"Server is released: {_serverToken}");
+ await _clientContext.ReleaseAsync(_clientToken);
+ _logger.WriteLine($"Client is released: {_clientToken}");
+ _endPoint.Dispose();
+ }
+
+ private sealed class TestServer1 : ServerService, ITestService1
+ {
+ public void Invoke() => Client.OnInvoked();
+ }
+}
diff --git a/test/JSSoft.Communication.Tests/CallbackTest.cs b/test/JSSoft.Communication.Tests/CallbackTest.cs
index 02670a5..42c73a2 100644
--- a/test/JSSoft.Communication.Tests/CallbackTest.cs
+++ b/test/JSSoft.Communication.Tests/CallbackTest.cs
@@ -10,23 +10,28 @@ namespace JSSoft.Communication.Tests;
public class CallbackTest : IAsyncLifetime
{
- private const int Timeout = 30000;
+ private const int ClientCount = 2;
private readonly ITestOutputHelper _logger;
private readonly TestServer _testServer = new();
- private readonly TestClient _testClient = new();
private readonly ServerContext _serverContext;
- private readonly ClientContext _clientContext;
+ private readonly TestClient[] _testClients = new TestClient[ClientCount];
+ private readonly ClientContext[] _clientContexts = new ClientContext[ClientCount];
+ private readonly Guid[] _clientTokens = new Guid[ClientCount];
private readonly RandomEndPoint _endPoint = new();
- private ITestService? _server;
+ private TestServer? _server;
- private Guid _clientToken;
private Guid _serverToken;
public CallbackTest(ITestOutputHelper logger)
{
_logger = logger;
_serverContext = new(_testServer) { EndPoint = _endPoint };
- _clientContext = new(_testClient) { EndPoint = _endPoint };
+ for (var i = 0; i < ClientCount; i++)
+ {
+ _testClients[i] = new() { Index = i };
+ _clientContexts[i] = new(_testClients[i]) { EndPoint = _endPoint };
+ }
+
logger.WriteLine($"{_endPoint}");
}
@@ -49,55 +54,63 @@ public interface ITestCallback
}
[Fact]
- public void Callback1_Test()
+ public async Task Callback1_TestAsync()
{
- var raised = Assert.Raises(
- handler => _testClient.Invoked += handler,
- handler => _testClient.Invoked -= handler,
- () =>
+ var raisedCount = await EventTestUtility.RaisesManyAsync(
+ items: _testClients,
+ attach: (item, handler) => item.Invoked += handler,
+ detach: (item, handler) => item.Invoked -= handler,
+ testCode: () =>
{
_server!.Invoke();
- _testClient.AutoResetEvent.WaitOne(Timeout);
});
- Assert.Null(raised.Arguments.Value);
+
+ Assert.Equal(_testClients.Length, raisedCount);
+ Assert.All(_testClients, item => Assert.Null(item.Value));
}
[Fact]
- public void Callback2_Test()
+ public async Task Callback2_TestAsync()
{
var value = 123;
- var raised = Assert.Raises(
- handler => _testClient.Invoked += handler,
- handler => _testClient.Invoked -= handler,
- () =>
+ var raisedCount = await EventTestUtility.RaisesManyAsync(
+ items: _testClients,
+ attach: (item, handler) => item.Invoked += handler,
+ detach: (item, handler) => item.Invoked -= handler,
+ testCode: () =>
{
_server!.Invoke(value);
- _testClient.AutoResetEvent.WaitOne(Timeout);
});
- Assert.Equal(value, raised.Arguments.Value);
+ Assert.Equal(_testClients.Length, raisedCount);
+ Assert.All(_testClients, item => Assert.Equal(value, item.Value));
}
[Fact]
- public void Callback3_Test()
+ public async Task Callback3_TestAsync()
{
var value = (123, "123");
- var raised = Assert.Raises(
- handler => _testClient.Invoked += handler,
- handler => _testClient.Invoked -= handler,
- () =>
+ var raisedCount = await EventTestUtility.RaisesManyAsync(
+ items: _testClients,
+ attach: (item, handler) => item.Invoked += handler,
+ detach: (item, handler) => item.Invoked -= handler,
+ testCode: () =>
{
_server!.Invoke(value);
- _testClient.AutoResetEvent.WaitOne(Timeout);
});
- Assert.Equal(value, raised.Arguments.Value);
+ Assert.Equal(_testClients.Length, raisedCount);
+ Assert.All(_testClients, item => Assert.Equal(value, item.Value));
}
public async Task InitializeAsync()
{
_serverToken = await _serverContext.OpenAsync(CancellationToken.None);
_logger.WriteLine($"Server is opened: {_serverToken}");
- _clientToken = await _clientContext.OpenAsync(CancellationToken.None);
- _logger.WriteLine($"Client is opened: {_clientToken}");
+ for (var i = 0; i < ClientCount; i++)
+ {
+ _clientTokens[i] = await _clientContexts[i].OpenAsync(CancellationToken.None);
+ _logger.WriteLine($"Client #{i} is opened: {_clientTokens[i]}");
+ }
+
_server = _testServer;
}
@@ -105,8 +118,12 @@ public async Task DisposeAsync()
{
await _serverContext.ReleaseAsync(_serverToken);
_logger.WriteLine($"Server is released: {_serverToken}");
- await _clientContext.ReleaseAsync(_clientToken);
- _logger.WriteLine($"Client is released: {_clientToken}");
+ for (var i = 0; i < ClientCount; i++)
+ {
+ await _clientContexts[i].ReleaseAsync(_clientTokens[i]);
+ _logger.WriteLine($"Client #{i} is released: {_clientTokens[i]}");
+ }
+
_endPoint.Dispose();
}
@@ -130,20 +147,27 @@ private sealed class TestClient : ClientService, IT
public AutoResetEvent AutoResetEvent { get; } = new(initialState: false);
+ public int Index { get; set; } = -1;
+
+ public object? Value { get; private set; } = DBNull.Value;
+
void ITestCallback.OnInvoked()
{
+ Value = null;
Invoked?.Invoke(this, new(null));
AutoResetEvent.Set();
}
void ITestCallback.OnInvoked(int value)
{
+ Value = value;
Invoked?.Invoke(this, new(value));
AutoResetEvent.Set();
}
void ITestCallback.OnInvoked((int Value1, string Value2) value)
{
+ Value = value;
Invoked?.Invoke(this, new(value));
AutoResetEvent.Set();
}
diff --git a/test/JSSoft.Communication.Tests/EventObjectCollection.cs b/test/JSSoft.Communication.Tests/EventObjectCollection.cs
new file mode 100644
index 0000000..0e513b9
--- /dev/null
+++ b/test/JSSoft.Communication.Tests/EventObjectCollection.cs
@@ -0,0 +1,53 @@
+//
+// Copyright (c) 2024 Jeesu Choi. All Rights Reserved.
+// Licensed under the MIT License. See LICENSE.md in the project root for license information.
+//
+
+namespace JSSoft.Communication.Tests;
+
+public sealed class EventObjectCollection
+ : Dictionary, IDisposable
+ where TObject : notnull
+ where TEventArgs : EventArgs
+{
+ private readonly Action> _detach;
+
+ public EventObjectCollection(
+ TObject[] items,
+ Action> attach,
+ Action> detach)
+ : base(capacity: items.Length)
+ {
+ for (var i = 0; i < items.Length; i++)
+ {
+ Add(items[i], new ManualResetEvent(initialState: false));
+ attach(items[i], Handler);
+ }
+
+ _detach = detach;
+ }
+
+ public void Dispose()
+ {
+ foreach (var item in this)
+ {
+ _detach(item.Key, Handler);
+ item.Value.Dispose();
+ }
+ }
+
+ public async Task WaiyAsync(int timeout)
+ {
+ var tasks = Values.Select(item => Task.Run(() => item.WaitOne(timeout)));
+ var results = await Task.WhenAll(tasks);
+ return results.Count(item => item == true);
+ }
+
+ private void Handler(object? s, TEventArgs args)
+ {
+ if (s is TObject item && TryGetValue(item, out var manualResetEvent) == true)
+ {
+ manualResetEvent.Set();
+ }
+ }
+}
diff --git a/test/JSSoft.Communication.Tests/EventTestUtility.cs b/test/JSSoft.Communication.Tests/EventTestUtility.cs
index 9232995..a630e25 100644
--- a/test/JSSoft.Communication.Tests/EventTestUtility.cs
+++ b/test/JSSoft.Communication.Tests/EventTestUtility.cs
@@ -7,6 +7,8 @@ namespace JSSoft.Communication.Tests;
public static class EventTestUtility
{
+ public const int Timeout = 10000;
+
public static async Task RaisesAsync(
Action attach, Action detach, Func testCode)
{
@@ -25,11 +27,39 @@ public static async Task RaisesAsync(
detach(Handler);
}
- return manualResetEvent.WaitOne(millisecondsTimeout: 10000);
+ return manualResetEvent.WaitOne(Timeout);
void Handler(object? s, EventArgs args)
{
manualResetEvent.Set();
}
}
+
+ public static async Task RaisesManyAsync(
+ TObject[] items,
+ Action> attach,
+ Action> detach,
+ Action testCode)
+ where TObject : notnull
+ where TEventArgs : EventArgs
+ {
+ using var eventByItem
+ = new EventObjectCollection(items, attach, detach);
+ testCode();
+ return await eventByItem.WaiyAsync(Timeout);
+ }
+
+ public static async Task RaisesManyAsync(
+ TObject[] items,
+ Action> attach,
+ Action> detach,
+ Func testCode)
+ where TObject : notnull
+ where TEventArgs : EventArgs
+ {
+ using var eventByItem
+ = new EventObjectCollection(items, attach, detach);
+ await testCode();
+ return await eventByItem.WaiyAsync(Timeout);
+ }
}