Refactor logger. (#1247)

This commit is contained in:
Christian
2021-09-26 11:05:00 +02:00
committed by GitHub
parent c756a4092f
commit 0920c86d37
70 changed files with 618 additions and 510 deletions
@@ -0,0 +1,160 @@
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client.Options;
using MQTTnet.Exceptions;
using MQTTnet.LowLevelClient;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client
{
[TestClass]
public class LowLevelMqttClient_Tests
{
public TestContext TestContext { get; set; }
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Not_Existing_Server()
{
var client = new MqttFactory().CreateLowLevelMqttClient();
var options = new MqttClientOptionsBuilder()
.WithTcpServer("localhost")
.Build();
await client.ConnectAsync(options, CancellationToken.None).ConfigureAwait(false);
}
[TestMethod]
public async Task Connect_And_Disconnect()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var factory = new MqttFactory();
var lowLevelClient = factory.CreateLowLevelMqttClient();
await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None);
await lowLevelClient.DisconnectAsync(CancellationToken.None);
}
}
[TestMethod]
public async Task Authenticate()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var factory = new MqttFactory();
var lowLevelClient = factory.CreateLowLevelMqttClient();
await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None);
var receivedPacket = await Authenticate(lowLevelClient).ConfigureAwait(false);
await lowLevelClient.DisconnectAsync(CancellationToken.None).ConfigureAwait(false);
Assert.IsNotNull(receivedPacket);
Assert.AreEqual(MqttConnectReturnCode.ConnectionAccepted, receivedPacket.ReturnCode);
}
}
[TestMethod]
public async Task Subscribe()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var factory = new MqttFactory();
var lowLevelClient = factory.CreateLowLevelMqttClient();
await lowLevelClient.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build(), CancellationToken.None);
await Authenticate(lowLevelClient).ConfigureAwait(false);
var receivedPacket = await Subscribe(lowLevelClient, "a").ConfigureAwait(false);
await lowLevelClient.DisconnectAsync(CancellationToken.None).ConfigureAwait(false);
Assert.IsNotNull(receivedPacket);
Assert.AreEqual(MqttSubscribeReturnCode.SuccessMaximumQoS0, receivedPacket.ReturnCodes[0]);
}
}
[TestMethod]
public async Task Loose_Connection()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
testEnvironment.ServerPort = 8364;
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectLowLevelClient(o => o.WithCommunicationTimeout(TimeSpan.Zero));
await Authenticate(client).ConfigureAwait(false);
await server.StopAsync();
await Task.Delay(1000);
try
{
await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None).ConfigureAwait(false);
await client.SendAsync(MqttPingReqPacket.Instance, CancellationToken.None).ConfigureAwait(false);
}
catch (MqttCommunicationException exception)
{
Assert.IsTrue(exception.InnerException is SocketException);
return;
}
catch
{
Assert.Fail("Wrong exception type thrown.");
}
Assert.Fail("This MUST fail");
}
}
async Task<MqttConnAckPacket> Authenticate(ILowLevelMqttClient client)
{
await client.SendAsync(new MqttConnectPacket
{
CleanSession = true,
ClientId = TestContext.TestName,
Username = "user",
Password = Encoding.UTF8.GetBytes("pass")
},
CancellationToken.None).ConfigureAwait(false);
return await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false) as MqttConnAckPacket;
}
async Task<MqttSubAckPacket> Subscribe(ILowLevelMqttClient client, string topic)
{
await client.SendAsync(new MqttSubscribePacket
{
PacketIdentifier = 1,
TopicFilters = new List<MqttTopicFilter>
{
new MqttTopicFilter
{
Topic = topic
}
}
},
CancellationToken.None).ConfigureAwait(false);
return await client.ReceiveAsync(CancellationToken.None).ConfigureAwait(false) as MqttSubAckPacket;
}
}
}
@@ -0,0 +1,508 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Diagnostics;
using MQTTnet.Diagnostics.Logger;
using MQTTnet.Extensions.ManagedClient;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client
{
[TestClass]
public class ManagedMqttClient_Tests
{
public TestContext TestContext { get; set; }
[TestMethod]
public async Task Drop_New_Messages_On_Full_Queue()
{
var factory = new MqttFactory();
var managedClient = factory.CreateManagedMqttClient();
try
{
var clientOptions = new ManagedMqttClientOptionsBuilder()
.WithMaxPendingMessages(5)
.WithPendingMessagesOverflowStrategy(MqttPendingMessagesOverflowStrategy.DropNewMessage);
clientOptions.WithClientOptions(o => o.WithTcpServer("localhost"));
await managedClient.StartAsync(clientOptions.Build());
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "1" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "2" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "3" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "4" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "5" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "6" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "7" });
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "8" });
Assert.AreEqual(5, managedClient.PendingApplicationMessagesCount);
}
finally
{
await managedClient.StopAsync();
}
}
[TestMethod]
public async Task ManagedClients_Will_Message_Send()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var receivedMessagesCount = 0;
await testEnvironment.StartServer();
var willMessage = new MqttApplicationMessageBuilder().WithTopic("My/last/will").WithAtMostOnceQoS().Build();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort)
.WithWillMessage(willMessage);
var dyingClient = testEnvironment.CreateClient();
var dyingManagedClient = new ManagedMqttClient(dyingClient, testEnvironment.ClientLogger);
await dyingManagedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.Build());
var recievingClient = await testEnvironment.ConnectClient();
await recievingClient.SubscribeAsync("My/last/will");
recievingClient.UseApplicationMessageReceivedHandler(context => Interlocked.Increment(ref receivedMessagesCount));
dyingManagedClient.Dispose();
await Task.Delay(1000);
Assert.AreEqual(1, receivedMessagesCount);
}
}
[TestMethod]
public async Task Start_Stop()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort);
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.Build());
await connected;
await managedClient.StopAsync();
await Task.Delay(500);
Assert.AreEqual(0, (await server.GetClientStatusAsync()).Count);
}
}
[TestMethod]
public async Task Storage_Queue_Drains()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
testEnvironment.IgnoreClientLogErrors = true;
testEnvironment.IgnoreServerLogErrors = true;
await testEnvironment.StartServer();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort);
var storage = new ManagedMqttClientTestStorage();
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.WithStorage(storage)
.WithAutoReconnectDelay(System.TimeSpan.FromSeconds(5))
.Build());
await connected;
await testEnvironment.Server.StopAsync();
await managedClient.PublishAsync(new MqttApplicationMessage { Topic = "1" });
//Message should have been added to the storage queue in PublishAsync,
//and we are awaiting PublishAsync, so the message should already be
//in storage at this point (i.e. no waiting).
Assert.AreEqual(1, storage.GetMessageCount());
connected = GetConnectedTask(managedClient);
await testEnvironment.Server.StartAsync(new MqttServerOptionsBuilder()
.WithDefaultEndpointPort(testEnvironment.ServerPort).Build());
await connected;
//Wait 500ms here so the client has time to publish the queued message
await Task.Delay(500);
Assert.AreEqual(0, storage.GetMessageCount());
await managedClient.StopAsync();
}
}
[TestMethod]
public async Task Subscriptions_And_Unsubscriptions_Are_Made_And_Reestablished_At_Reconnect()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var unmanagedClient = testEnvironment.CreateClient();
var managedClient = await CreateManagedClientAsync(testEnvironment, unmanagedClient);
var received = SetupReceivingOfMessages(managedClient, 2);
// Perform some opposing subscriptions and unsubscriptions to verify
// that these conflicting subscriptions are handled correctly
await managedClient.SubscribeAsync("keptSubscribed");
await managedClient.SubscribeAsync("subscribedThenUnsubscribed");
await managedClient.UnsubscribeAsync("subscribedThenUnsubscribed");
await managedClient.UnsubscribeAsync("unsubscribedThenSubscribed");
await managedClient.SubscribeAsync("unsubscribedThenSubscribed");
//wait a bit for the subscriptions to become established before the messages are published
await Task.Delay(500);
var sendingClient = await testEnvironment.ConnectClient();
async Task PublishMessages()
{
await sendingClient.PublishAsync("keptSubscribed", new byte[] { 1 });
await sendingClient.PublishAsync("subscribedThenUnsubscribed", new byte[] { 1 });
await sendingClient.PublishAsync("unsubscribedThenSubscribed", new byte[] { 1 });
}
await PublishMessages();
async Task AssertMessagesReceived()
{
var messages = await received;
Assert.AreEqual("keptSubscribed", messages[0].Topic);
Assert.AreEqual("unsubscribedThenSubscribed", messages[1].Topic);
}
await AssertMessagesReceived();
var connected = GetConnectedTask(managedClient);
await unmanagedClient.DisconnectAsync();
// the managed client has to reconnect by itself
await connected;
// wait a bit so that the managed client can reestablish the subscriptions
await Task.Delay(500);
received = SetupReceivingOfMessages(managedClient, 2);
await PublishMessages();
// and then the same subscriptions need to exist again
await AssertMessagesReceived();
}
}
// This case also serves as a regression test for the previous behavior which re-published
// each and every existing subscriptions with every new subscription that was made
// (causing performance problems and having the visible symptom of retained messages being received again)
[TestMethod]
public async Task Subscriptions_Subscribe_Only_New_Subscriptions()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var managedClient = await CreateManagedClientAsync(testEnvironment);
var sendingClient = await testEnvironment.ConnectClient();
await managedClient.SubscribeAsync("topic");
//wait a bit for the subscription to become established
await Task.Delay(500);
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var messages = await SetupReceivingOfMessages(managedClient, 1);
Assert.AreEqual(1, messages.Count);
Assert.AreEqual("topic", messages.Single().Topic);
await managedClient.SubscribeAsync("anotherTopic");
await Task.Delay(500);
// The subscription of the other topic must not trigger a re-subscription of the existing topic
// (and thus renewed receiving of the retained message)
Assert.AreEqual(1, messages.Count);
}
}
// This case also serves as a regression test for the previous behavior
// that subscriptions were only published at the ConnectionCheckInterval
[TestMethod]
public async Task Subscriptions_Are_Published_Immediately()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
// Use a long connection check interval to verify that the subscriptions
// do not depend on the connection check interval anymore
var connectionCheckInterval = TimeSpan.FromSeconds(10);
var managedClient = await CreateManagedClientAsync(testEnvironment, null, connectionCheckInterval);
var sendingClient = await testEnvironment.ConnectClient();
await sendingClient.PublishAsync(new MqttApplicationMessage { Topic = "topic", Payload = new byte[] { 1 }, Retain = true });
var subscribeTime = DateTime.UtcNow;
var messagesTask = SetupReceivingOfMessages(managedClient, 1);
await managedClient.SubscribeAsync("topic");
var messages = await messagesTask;
var elapsed = DateTime.UtcNow - subscribeTime;
Assert.IsTrue(elapsed < TimeSpan.FromSeconds(1), $"Subscriptions must be activated immediately, this one took {elapsed}");
Assert.AreEqual(messages.Single().Topic, "topic");
}
}
[TestMethod]
public async Task Subscriptions_Are_Cleared_At_Logout()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer().ConfigureAwait(false);
var sendingClient = await testEnvironment.ConnectClient().ConfigureAwait(false);
await sendingClient.PublishAsync(new MqttApplicationMessage
{
Topic = "topic",
Payload = new byte[] { 1 },
Retain = true
});
// Wait a bit for the retained message to be available
await Task.Delay(1000);
await sendingClient.DisconnectAsync();
// Now use the managed client and check if subscriptions get cleared properly.
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("127.0.0.1", testEnvironment.ServerPort);
var receivedManagedMessages = new List<MqttApplicationMessage>();
var managedClient = new ManagedMqttClient(testEnvironment.CreateClient(), new MqttNetEventLogger());
managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c =>
{
receivedManagedMessages.Add(c.ApplicationMessage);
});
await managedClient.SubscribeAsync("topic");
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.WithAutoReconnectDelay(TimeSpan.FromSeconds(1))
.Build());
await Task.Delay(1000);
Assert.AreEqual(1, receivedManagedMessages.Count);
await managedClient.StopAsync();
await Task.Delay(500);
await managedClient.StartAsync(new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.WithAutoReconnectDelay(TimeSpan.FromSeconds(1))
.Build());
await Task.Delay(1000);
// After reconnect and then some delay, the retained message must not be received,
// showing that the subscriptions were cleared
Assert.AreEqual(1, receivedManagedMessages.Count);
// Make sure that it gets received after subscribing again.
await managedClient.SubscribeAsync("topic");
await Task.Delay(500);
Assert.AreEqual(2, receivedManagedMessages.Count);
}
}
[TestMethod]
public async Task Manage_Session_MaxParallel_Subscribe()
{
using (var testEnvironment = new TestEnvironment())
{
testEnvironment.IgnoreClientLogErrors = true;
var serverOptions = new MqttServerOptionsBuilder();
await testEnvironment.StartServer(serverOptions);
var options = new MqttClientOptionsBuilder().WithClientId("1").WithKeepAlivePeriod(TimeSpan.FromSeconds(5));
var hasReceive = false;
void OnReceive()
{
if (!hasReceive)
{
hasReceive = true;
}
}
var clients = await Task.WhenAll(Enumerable.Range(0, 25)
.Select(i => TryConnect_Subscribe(testEnvironment, options, OnReceive)));
var connectedClients = clients.Where(c => c?.IsConnected ?? false).ToList();
Assert.AreEqual(1, connectedClients.Count);
await Task.Delay(10000); // over 1.5T
var option2 = new MqttClientOptionsBuilder().WithClientId("2").WithKeepAlivePeriod(TimeSpan.FromSeconds(10));
var sendClient = await testEnvironment.ConnectClient(option2);
await sendClient.PublishAsync("aaa", "1");
await Task.Delay(3000);
Assert.AreEqual(true, hasReceive);
}
}
private async Task<IMqttClient> TryConnect_Subscribe(TestEnvironment testEnvironment, MqttClientOptionsBuilder options, Action onReceive)
{
try
{
var sendClient = await testEnvironment.ConnectClient(options);
sendClient.ApplicationMessageReceivedHandler = new MQTTnet.Client.Receiving.MqttApplicationMessageReceivedHandlerDelegate(e =>
{
onReceive();
});
await sendClient.SubscribeAsync("aaa");
return sendClient;
}
catch (System.Exception)
{
return null;
}
}
async Task<ManagedMqttClient> CreateManagedClientAsync(
TestEnvironment testEnvironment,
IMqttClient underlyingClient = null,
TimeSpan? connectionCheckInterval = null)
{
await testEnvironment.StartServer();
var clientOptions = new MqttClientOptionsBuilder()
.WithTcpServer("localhost", testEnvironment.ServerPort);
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithClientOptions(clientOptions)
.Build();
// Use a short connection check interval so that subscription operations are performed quickly
// in order to verify against a previous implementation that performed subscriptions only
// at connection check intervals
managedOptions.ConnectionCheckInterval = connectionCheckInterval ?? TimeSpan.FromSeconds(0.1);
var managedClient = new ManagedMqttClient(underlyingClient ?? testEnvironment.CreateClient(), new MqttNetEventLogger());
var connected = GetConnectedTask(managedClient);
await managedClient.StartAsync(managedOptions);
await connected;
return managedClient;
}
/// <summary>
/// Returns a task that will finish when the <paramref name="managedClient"/> has connected
/// </summary>
Task GetConnectedTask(ManagedMqttClient managedClient)
{
TaskCompletionSource<bool> connected = new TaskCompletionSource<bool>();
managedClient.ConnectedHandler = new MqttClientConnectedHandlerDelegate(e =>
{
managedClient.ConnectedHandler = null;
connected.SetResult(true);
});
return connected.Task;
}
/// <summary>
/// Returns a task that will return the messages received on <paramref name="managedClient"/>
/// when <paramref name="expectedNumberOfMessages"/> have been received
/// </summary>
Task<List<MqttApplicationMessage>> SetupReceivingOfMessages(ManagedMqttClient managedClient, int expectedNumberOfMessages)
{
var receivedMessages = new List<MqttApplicationMessage>();
var result = new TaskCompletionSource<List<MqttApplicationMessage>>(TaskCreationOptions.RunContinuationsAsynchronously);
managedClient.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(r =>
{
receivedMessages.Add(r.ApplicationMessage);
if (receivedMessages.Count == expectedNumberOfMessages)
{
result.TrySetResult(receivedMessages);
}
});
return result.Task;
}
}
public class ManagedMqttClientTestStorage : IManagedMqttClientStorage
{
IList<ManagedMqttApplicationMessage> _messages;
public Task<IList<ManagedMqttApplicationMessage>> LoadQueuedMessagesAsync()
{
if (_messages == null)
{
_messages = new List<ManagedMqttApplicationMessage>();
}
return Task.FromResult(_messages);
}
public Task SaveQueuedMessagesAsync(IList<ManagedMqttApplicationMessage> messages)
{
_messages = messages;
return Task.FromResult(0);
}
public int GetMessageCount()
{
return _messages.Count;
}
}
}
@@ -0,0 +1,22 @@
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client.Options;
using MQTTnet.Extensions;
namespace MQTTnet.Tests.Client
{
[TestClass]
public class MqttClientOptionsBuilder_Tests
{
[TestMethod]
public void WithConnectionUri_Credential_Test()
{
var options = new MqttClientOptionsBuilder()
.WithConnectionUri("mqtt://user:password@127.0.0.1")
.Build();
Assert.AreEqual("user", options.Credentials.Username);
Assert.IsTrue(Encoding.UTF8.GetBytes("password").SequenceEqual(options.Credentials.Password));
}
}
}
@@ -0,0 +1,874 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Client.Subscribing;
using MQTTnet.Exceptions;
using MQTTnet.Formatter;
using MQTTnet.Packets;
using MQTTnet.Protocol;
using MQTTnet.Server;
using MQTTnet.Tests.Mockups;
namespace MQTTnet.Tests.Client
{
[TestClass]
public class Client_Tests
{
public TestContext TestContext { get; set; }
[TestMethod]
public async Task Ensure_Queue_Drain()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectLowLevelClient();
var i = 0;
server.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c =>
{
i++;
});
await client.SendAsync(new MqttConnectPacket
{
ClientId = "Ensure_Queue_Drain_Test"
}, CancellationToken.None);
await client.SendAsync(new MqttPublishPacket
{
Topic = "Test"
}, CancellationToken.None);
await Task.Delay(500);
// This will simulate a device which closes the connection directly
// after sending the data so do delay is added between send and dispose!
client.Dispose();
await Task.Delay(1000);
Assert.AreEqual(1, i);
}
}
[TestMethod]
public async Task Set_ClientWasConnected_On_ServerDisconnect()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
client.UseDisconnectedHandler(e => Assert.IsTrue(e.ClientWasConnected));
await server.StopAsync();
await Task.Delay(4000);
}
}
[TestMethod]
public async Task Set_ClientWasConnected_On_ClientDisconnect()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
client.UseDisconnectedHandler(e => Assert.IsTrue(e.ClientWasConnected));
await client.DisconnectAsync();
await Task.Delay(200);
}
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationTimedOutException))]
public async Task Connect_To_Invalid_Server_Wrong_IP()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").WithCommunicationTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Port_Not_Opened()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", 12345).WithCommunicationTimeout(TimeSpan.FromSeconds(5)).Build());
}
[TestMethod]
[ExpectedException(typeof(MqttCommunicationException))]
public async Task Connect_To_Invalid_Server_Wrong_Protocol()
{
var client = new MqttFactory().CreateMqttClient();
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("http://127.0.0.1", 12345).WithCommunicationTimeout(TimeSpan.FromSeconds(2)).Build());
}
[TestMethod]
public async Task Send_Manual_Ping()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
await client.PingAsync(CancellationToken.None);
}
}
[TestMethod]
public async Task Send_Reply_In_Message_Handler_For_Same_Client()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
await client.SubscribeAsync("#");
var replyReceived = false;
client.UseApplicationMessageReceivedHandler(c =>
{
if (c.ApplicationMessage.Topic == "request")
{
#pragma warning disable 4014
Task.Run(() => client.PublishAsync("reply", null, MqttQualityOfServiceLevel.AtLeastOnce));
#pragma warning restore 4014
}
else
{
replyReceived = true;
}
});
await client.PublishAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce);
SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10));
Assert.IsTrue(replyReceived);
}
}
[TestMethod]
public async Task Send_Reply_In_Message_Handler()
{
using (var testEnvironment = new TestEnvironment())
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
var client2 = await testEnvironment.ConnectClient();
await client1.SubscribeAsync("#");
await client2.SubscribeAsync("#");
var replyReceived = false;
client1.UseApplicationMessageReceivedHandler(c =>
{
if (c.ApplicationMessage.Topic == "reply")
{
replyReceived = true;
}
});
client2.UseApplicationMessageReceivedHandler(async c =>
{
if (c.ApplicationMessage.Topic == "request")
{
// Use AtMostOnce here because with QoS 1 or even QoS 2 the process waits for
// the ACK etc. The problem is that the SpinUntil below only waits until the
// flag is set. It does not wait until the client has sent the ACK
await client2.PublishAsync("reply", null, MqttQualityOfServiceLevel.AtMostOnce);
}
});
await client1.PublishAsync("request", null, MqttQualityOfServiceLevel.AtLeastOnce);
await Task.Delay(500);
SpinWait.SpinUntil(() => replyReceived, TimeSpan.FromSeconds(10));
await Task.Delay(500);
Assert.IsTrue(replyReceived);
}
}
[TestMethod]
public async Task Reconnect()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
await Task.Delay(500);
Assert.IsTrue(client.IsConnected);
await server.StopAsync();
await Task.Delay(500);
Assert.IsFalse(client.IsConnected);
await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultEndpointPort(testEnvironment.ServerPort).Build());
await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(client.IsConnected);
}
}
[TestMethod]
public async Task Reconnect_While_Server_Offline()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
testEnvironment.IgnoreClientLogErrors = true;
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
await Task.Delay(500);
Assert.IsTrue(client.IsConnected);
await server.StopAsync();
await Task.Delay(500);
Assert.IsFalse(client.IsConnected);
for (var i = 0; i < 5; i++)
{
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.Fail("Must fail!");
}
catch
{
}
}
await server.StartAsync(new MqttServerOptionsBuilder().WithDefaultEndpointPort(testEnvironment.ServerPort).Build());
await Task.Delay(500);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.IsTrue(client.IsConnected);
}
}
[TestMethod]
public async Task Reconnect_From_Disconnected_Event()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
testEnvironment.IgnoreClientLogErrors = true;
var client = testEnvironment.CreateClient();
var tries = 0;
var maxTries = 3;
client.UseDisconnectedHandler(async e =>
{
if (tries >= maxTries)
{
return;
}
Interlocked.Increment(ref tries);
await Task.Delay(100);
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
});
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("127.0.0.1", testEnvironment.ServerPort).Build());
Assert.Fail("Must fail!");
}
catch
{
}
SpinWait.SpinUntil(() => tries >= maxTries, 10000);
Assert.AreEqual(maxTries, tries);
}
}
[TestMethod]
public async Task PacketIdentifier_In_Publish_Result()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
var result = await client.PublishAsync("a", "a", MqttQualityOfServiceLevel.AtMostOnce);
Assert.AreEqual(null, result.PacketIdentifier);
result = await client.PublishAsync("b", "b", MqttQualityOfServiceLevel.AtMostOnce);
Assert.AreEqual(null, result.PacketIdentifier);
result = await client.PublishAsync("a", "a", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual((ushort)1, result.PacketIdentifier);
result = await client.PublishAsync("b", "b", MqttQualityOfServiceLevel.AtLeastOnce);
Assert.AreEqual((ushort)2, result.PacketIdentifier);
result = await client.PublishAsync("a", "a", MqttQualityOfServiceLevel.ExactlyOnce);
Assert.AreEqual((ushort)3, result.PacketIdentifier);
result = await client.PublishAsync("b", "b", MqttQualityOfServiceLevel.ExactlyOnce);
Assert.AreEqual((ushort)4, result.PacketIdentifier);
}
}
[TestMethod]
public async Task Invalid_Connect_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build());
Assert.Fail("Must fail!");
}
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
}
}
}
[TestMethod]
public async Task ConnectTimeout_Throws_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
bool disconnectHandlerCalled = false;
try
{
client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(args =>
{
disconnectHandlerCalled = true;
});
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("1.2.3.4").Build());
Assert.Fail("Must fail!");
}
catch (Exception exception)
{
Assert.IsNotNull(exception);
Assert.IsInstanceOfType(exception, typeof(MqttCommunicationException));
//Assert.IsInstanceOfType(exception.InnerException, typeof(SocketException));
}
await Task.Delay(100); // disconnected handler is called async
Assert.IsTrue(disconnectHandlerCalled);
}
}
[TestMethod]
public async Task Fire_Disconnected_Event_On_Server_Shutdown()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
var server = await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
var handlerFired = false;
client.UseDisconnectedHandler(e => handlerFired = true);
await server.StopAsync();
await Task.Delay(4000);
Assert.IsTrue(handlerFired);
}
}
[TestMethod]
public async Task Disconnect_Event_Contains_Exception()
{
var factory = new MqttFactory();
using (var client = factory.CreateMqttClient())
{
Exception ex = null;
client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(e =>
{
ex = e.Exception;
});
try
{
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("wrong-server").Build());
}
catch
{
}
await Task.Delay(500);
Assert.IsNotNull(ex);
Assert.IsInstanceOfType(ex, typeof(MqttCommunicationException));
Assert.IsInstanceOfType(ex.InnerException, typeof(SocketException));
}
}
[TestMethod]
public async Task Preserve_Message_Order()
{
// The messages are sent in reverse or to ensure that the delay in the handler
// needs longer for the first messages and later messages may be processed earlier (if there
// is an issue).
const int MessagesCount = 50;
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
await client1.SubscribeAsync("x");
var receivedValues = new List<int>();
async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs)
{
var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString());
await Task.Delay(value);
lock (receivedValues)
{
receivedValues.Add(value);
}
}
client1.UseApplicationMessageReceivedHandler(Handler1);
var client2 = await testEnvironment.ConnectClient();
for (var i = MessagesCount; i > 0; i--)
{
await client2.PublishAsync("x", i.ToString());
}
await Task.Delay(5000);
for (var i = MessagesCount; i > 0; i--)
{
Assert.AreEqual(i, receivedValues[MessagesCount - i]);
}
}
}
[TestMethod]
public async Task Preserve_Message_Order_With_Delayed_Acknowledgement()
{
// The messages are sent in reverse or to ensure that the delay in the handler
// needs longer for the first messages and later messages may be processed earlier (if there
// is an issue).
const int MessagesCount = 50;
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.ExactlyOnce);
var receivedValues = new List<int>();
Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs)
{
var value = int.Parse(eventArgs.ApplicationMessage.ConvertPayloadToString());
eventArgs.AutoAcknowledge = false;
Task.Delay(value).ContinueWith(x => eventArgs.AcknowledgeAsync(CancellationToken.None));
System.Diagnostics.Debug.WriteLine($"received {value}");
lock (receivedValues)
{
receivedValues.Add(value);
}
return Task.CompletedTask;
}
client1.UseApplicationMessageReceivedHandler(Handler1);
var client2 = await testEnvironment.ConnectClient();
for (var i = MessagesCount; i > 0; i--)
{
await client2.PublishAsync("x", i.ToString(), MqttQualityOfServiceLevel.ExactlyOnce);
}
await Task.Delay(5000);
for (var i = MessagesCount; i > 0; i--)
{
Assert.AreEqual(i, receivedValues[MessagesCount - i]);
}
}
}
[TestMethod]
public async Task Send_Reply_For_Any_Received_Message()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient();
await client1.SubscribeAsync("request/+");
async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs)
{
await client1.PublishAsync($"reply/{eventArgs.ApplicationMessage.Topic}");
}
client1.UseApplicationMessageReceivedHandler(Handler1);
var client2 = await testEnvironment.ConnectClient();
await client2.SubscribeAsync("reply/#");
var replies = new List<string>();
void Handler2(MqttApplicationMessageReceivedEventArgs eventArgs)
{
lock (replies)
{
replies.Add(eventArgs.ApplicationMessage.Topic);
}
}
client2.UseApplicationMessageReceivedHandler((Action<MqttApplicationMessageReceivedEventArgs>)Handler2);
await Task.Delay(500);
await client2.PublishAsync("request/a");
await client2.PublishAsync("request/b");
await client2.PublishAsync("request/c");
await Task.Delay(500);
Assert.AreEqual("reply/request/a,reply/request/b,reply/request/c", string.Join(",", replies));
}
}
[TestMethod]
public async Task Publish_With_Correct_Retain_Flag()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var receivedMessages = new List<MqttApplicationMessage>();
var client1 = await testEnvironment.ConnectClient();
client1.UseApplicationMessageReceivedHandler(c =>
{
lock (receivedMessages)
{
receivedMessages.Add(c.ApplicationMessage);
}
});
await client1.SubscribeAsync("a");
var client2 = await testEnvironment.ConnectClient();
var message = new MqttApplicationMessageBuilder().WithTopic("a").WithRetainFlag().Build();
await client2.PublishAsync(message);
await Task.Delay(500);
Assert.AreEqual(1, receivedMessages.Count);
Assert.IsFalse(receivedMessages.First().Retain); // Must be false even if set above!
}
}
[TestMethod]
public async Task Publish_QoS_1_In_ApplicationMessageReceiveHandler()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
const string client1Topic = "client1/topic";
const string client2Topic = "client2/topic";
const string expectedClient2Message = "hello client2";
var client1 = await testEnvironment.ConnectClient();
client1.UseApplicationMessageReceivedHandler(async c =>
{
await client1.PublishAsync(client2Topic, expectedClient2Message, MqttQualityOfServiceLevel.AtLeastOnce);
});
await client1.SubscribeAsync(client1Topic, MqttQualityOfServiceLevel.AtLeastOnce);
var client2 = await testEnvironment.ConnectClient();
var client2TopicResults = new List<string>();
client2.UseApplicationMessageReceivedHandler(c =>
{
client2TopicResults.Add(Encoding.UTF8.GetString(c.ApplicationMessage.Payload));
});
await client2.SubscribeAsync(client2Topic);
var client3 = await testEnvironment.ConnectClient();
var message = new MqttApplicationMessageBuilder().WithTopic(client1Topic).Build();
await client3.PublishAsync(message);
await client3.PublishAsync(message);
await Task.Delay(500);
Assert.AreEqual(2, client2TopicResults.Count);
Assert.AreEqual(expectedClient2Message, client2TopicResults[0]);
Assert.AreEqual(expectedClient2Message, client2TopicResults[1]);
}
}
[TestMethod]
public async Task Subscribe_In_Callback_Events()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var receivedMessages = new List<MqttApplicationMessage>();
var client = testEnvironment.CreateClient();
client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(async e =>
{
await client.SubscribeAsync("RCU/P1/H0001/R0003");
var msg = new MqttApplicationMessageBuilder()
.WithPayload("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|")
.WithTopic("RCU/P1/H0001/R0003");
await client.PublishAsync(msg.Build());
});
client.UseApplicationMessageReceivedHandler(c =>
{
lock (receivedMessages)
{
receivedMessages.Add(c.ApplicationMessage);
}
});
await client.ConnectAsync(new MqttClientOptionsBuilder().WithTcpServer("localhost", testEnvironment.ServerPort).Build());
await Task.Delay(500);
Assert.AreEqual(1, receivedMessages.Count);
Assert.AreEqual("DA|18RS00SC00XI0000RV00R100R200R300R400L100L200L300L400Y100Y200AC0102031800BELK0000BM0000|", receivedMessages.First().ConvertPayloadToString());
}
}
[TestMethod]
public async Task Message_Send_Retry()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
testEnvironment.IgnoreClientLogErrors = true;
testEnvironment.IgnoreServerLogErrors = true;
await testEnvironment.StartServer(
new MqttServerOptionsBuilder()
.WithPersistentSessions()
.WithDefaultCommunicationTimeout(TimeSpan.FromMilliseconds(250)));
var client1 = await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithCleanSession(false));
await client1.SubscribeAsync("x", MqttQualityOfServiceLevel.AtLeastOnce);
var retries = 0;
async Task Handler1(MqttApplicationMessageReceivedEventArgs eventArgs)
{
retries++;
await Task.Delay(1000);
throw new Exception("Broken!");
}
client1.UseApplicationMessageReceivedHandler(Handler1);
var client2 = await testEnvironment.ConnectClient();
await client2.PublishAsync("x");
await Task.Delay(3000);
// The server should disconnect clients which are not responding.
Assert.IsFalse(client1.IsConnected);
await client1.ReconnectAsync().ConfigureAwait(false);
await Task.Delay(1000);
Assert.AreEqual(2, retries);
}
}
[TestMethod]
public async Task NoConnectedHandler_Connect_DoesNotThrowException()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
}
}
[TestMethod]
public async Task NoDisconnectedHandler_Disconnect_DoesNotThrowException()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var client = await testEnvironment.ConnectClient();
Assert.IsTrue(client.IsConnected);
await client.DisconnectAsync();
Assert.IsFalse(client.IsConnected);
}
}
[TestMethod]
public async Task Frequent_Connects()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var clients = new List<IMqttClient>();
for (var i = 0; i < 100; i++)
{
clients.Add(await testEnvironment.ConnectClient(new MqttClientOptionsBuilder().WithClientId("a")));
}
await Task.Delay(500);
var clientStatus = await testEnvironment.Server.GetClientStatusAsync();
var sessionStatus = await testEnvironment.Server.GetSessionStatusAsync();
for (var i = 0; i < 98; i++)
{
Assert.IsFalse(clients[i].IsConnected, $"clients[{i}] is not connected");
}
Assert.IsTrue(clients[99].IsConnected);
Assert.AreEqual(1, clientStatus.Count);
Assert.AreEqual(1, sessionStatus.Count);
var receiveClient = clients[99];
object receivedPayload = null;
receiveClient.UseApplicationMessageReceivedHandler(e =>
{
receivedPayload = e.ApplicationMessage.ConvertPayloadToString();
});
await receiveClient.SubscribeAsync("x");
var sendClient = await testEnvironment.ConnectClient();
await sendClient.PublishAsync("x", "1");
await Task.Delay(250);
Assert.AreEqual("1", receivedPayload);
}
}
[TestMethod]
public async Task No_Payload()
{
using (var testEnvironment = new TestEnvironment(TestContext))
{
await testEnvironment.StartServer();
var sender = await testEnvironment.ConnectClient();
var receiver = await testEnvironment.ConnectClient();
var message = new MqttApplicationMessageBuilder()
.WithTopic("A");
await receiver.SubscribeAsync(new MqttClientSubscribeOptions
{
TopicFilters = new List<MqttTopicFilter> { new MqttTopicFilter { Topic = "#" } }
}, CancellationToken.None);
MqttApplicationMessage receivedMessage = null;
receiver.UseApplicationMessageReceivedHandler(e => receivedMessage = e.ApplicationMessage);
await sender.PublishAsync(message.Build(), CancellationToken.None);
await Task.Delay(1000);
Assert.IsNotNull(receivedMessage);
Assert.AreEqual("A", receivedMessage.Topic);
Assert.AreEqual(null, receivedMessage.Payload);
}
}
[TestMethod]
public async Task Subscribe_With_QoS2()
{
using (var testEnvironment = new TestEnvironment())
{
await testEnvironment.StartServer();
var client1 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500));
var client2 = await testEnvironment.ConnectClient(o => o.WithProtocolVersion(MqttProtocolVersion.V500));
var disconnectedFired = false;
client1.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(c =>
{
disconnectedFired = true;
});
var messageReceived = false;
client1.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(c =>
{
messageReceived = true;
});
await client1.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("topic1").WithExactlyOnceQoS().Build());
await Task.Delay(500);
var message = new MqttApplicationMessageBuilder().WithTopic("topic1").WithPayload("Hello World").WithExactlyOnceQoS().WithRetainFlag().Build();
await client2.PublishAsync(message);
await Task.Delay(500);
Assert.IsTrue(messageReceived);
Assert.IsTrue(client1.IsConnected);
Assert.IsFalse(disconnectedFired);
}
}
}
}