You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

248 lines
7.7 KiB

  1. using Microsoft.VisualStudio.TestTools.UnitTesting;
  2. using MQTTnet.Client;
  3. using MQTTnet.Client.Options;
  4. using MQTTnet.Diagnostics;
  5. using MQTTnet.Server;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.Linq;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using MQTTnet.Extensions.Rpc;
  13. using MQTTnet.Extensions.Rpc.Options;
  14. using MQTTnet.LowLevelClient;
  15. namespace MQTTnet.Tests.Mockups
  16. {
  17. public sealed class TestEnvironment : IDisposable
  18. {
  19. readonly MqttFactory _mqttFactory = new MqttFactory();
  20. readonly List<IMqttClient> _clients = new List<IMqttClient>();
  21. readonly List<string> _serverErrors = new List<string>();
  22. readonly List<string> _clientErrors = new List<string>();
  23. readonly List<Exception> _exceptions = new List<Exception>();
  24. public IMqttServer Server { get; private set; }
  25. public bool IgnoreClientLogErrors { get; set; }
  26. public bool IgnoreServerLogErrors { get; set; }
  27. public int ServerPort { get; set; } = 1888;
  28. public MqttNetLogger ServerLogger { get; } = new MqttNetLogger("server");
  29. public MqttNetLogger ClientLogger { get; } = new MqttNetLogger("client");
  30. public TestContext TestContext { get; }
  31. public TestEnvironment() : this(null)
  32. {
  33. }
  34. public TestEnvironment(TestContext testContext)
  35. {
  36. TestContext = testContext;
  37. ServerLogger.LogMessagePublished += (s, e) =>
  38. {
  39. if (Debugger.IsAttached)
  40. {
  41. Debug.WriteLine(e.LogMessage.ToString());
  42. }
  43. if (e.LogMessage.Level == MqttNetLogLevel.Error)
  44. {
  45. lock (_serverErrors)
  46. {
  47. _serverErrors.Add(e.LogMessage.ToString());
  48. }
  49. }
  50. };
  51. ClientLogger.LogMessagePublished += (s, e) =>
  52. {
  53. if (Debugger.IsAttached)
  54. {
  55. Debug.WriteLine(e.LogMessage.ToString());
  56. }
  57. if (e.LogMessage.Level == MqttNetLogLevel.Error)
  58. {
  59. lock (_clientErrors)
  60. {
  61. _clientErrors.Add(e.LogMessage.ToString());
  62. }
  63. }
  64. };
  65. }
  66. public async Task<IMqttRpcClient> ConnectRpcClientAsync(IMqttRpcClientOptions options)
  67. {
  68. return new MqttRpcClient(await ConnectClientAsync(), options);
  69. }
  70. public IMqttClient CreateClient()
  71. {
  72. lock (_clients)
  73. {
  74. var client = _mqttFactory.CreateMqttClient(ClientLogger);
  75. _clients.Add(client);
  76. return new TestClientWrapper(client, TestContext);
  77. }
  78. }
  79. public Task<IMqttServer> StartServerAsync()
  80. {
  81. return StartServerAsync(new MqttServerOptionsBuilder());
  82. }
  83. public async Task<IMqttServer> StartServerAsync(MqttServerOptionsBuilder options)
  84. {
  85. if (options == null) throw new ArgumentNullException(nameof(options));
  86. if (Server != null)
  87. {
  88. throw new InvalidOperationException("Server already started.");
  89. }
  90. Server = new TestServerWrapper(_mqttFactory.CreateMqttServer(ServerLogger), TestContext, this);
  91. options.WithDefaultEndpointPort(ServerPort);
  92. options.WithMaxPendingMessagesPerClient(int.MaxValue);
  93. await Server.StartAsync(options.Build()).ConfigureAwait(false);
  94. return Server;
  95. }
  96. public Task<IMqttClient> ConnectClientAsync()
  97. {
  98. return ConnectClientAsync(new MqttClientOptionsBuilder());
  99. }
  100. public Task<ILowLevelMqttClient> ConnectLowLevelClientAsync()
  101. {
  102. return ConnectLowLevelClientAsync(o => { });
  103. }
  104. public async Task<ILowLevelMqttClient> ConnectLowLevelClientAsync(Action<MqttClientOptionsBuilder> optionsBuilder)
  105. {
  106. if (optionsBuilder == null) throw new ArgumentNullException(nameof(optionsBuilder));
  107. var options = new MqttClientOptionsBuilder();
  108. options = options.WithTcpServer("127.0.0.1", ServerPort);
  109. optionsBuilder.Invoke(options);
  110. var client = new MqttFactory().CreateLowLevelMqttClient();
  111. await client.ConnectAsync(options.Build(), CancellationToken.None).ConfigureAwait(false);
  112. return client;
  113. }
  114. public async Task<IMqttClient> ConnectClientAsync(Action<MqttClientOptionsBuilder> optionsBuilder)
  115. {
  116. if (optionsBuilder == null) throw new ArgumentNullException(nameof(optionsBuilder));
  117. var options = new MqttClientOptionsBuilder();
  118. options = options.WithTcpServer("localhost", ServerPort);
  119. optionsBuilder.Invoke(options);
  120. var client = CreateClient();
  121. await client.ConnectAsync(options.Build()).ConfigureAwait(false);
  122. return client;
  123. }
  124. public async Task<IMqttClient> ConnectClientAsync(MqttClientOptionsBuilder options)
  125. {
  126. if (options == null) throw new ArgumentNullException(nameof(options));
  127. options = options.WithTcpServer("localhost", ServerPort);
  128. var client = CreateClient();
  129. await client.ConnectAsync(options.Build()).ConfigureAwait(false);
  130. return client;
  131. }
  132. public async Task<IMqttClient> ConnectClientAsync(IMqttClientOptions options)
  133. {
  134. if (options == null) throw new ArgumentNullException(nameof(options));
  135. var client = CreateClient();
  136. await client.ConnectAsync(options).ConfigureAwait(false);
  137. return client;
  138. }
  139. public void ThrowIfLogErrors()
  140. {
  141. lock (_serverErrors)
  142. {
  143. if (!IgnoreServerLogErrors && _serverErrors.Count > 0)
  144. {
  145. throw new Exception($"Server had {_serverErrors.Count} errors (${string.Join(Environment.NewLine, _serverErrors)}).");
  146. }
  147. }
  148. lock (_clientErrors)
  149. {
  150. if (!IgnoreClientLogErrors && _clientErrors.Count > 0)
  151. {
  152. throw new Exception($"Client(s) had {_clientErrors.Count} errors (${string.Join(Environment.NewLine, _clientErrors)}).");
  153. }
  154. }
  155. }
  156. public void Dispose()
  157. {
  158. foreach (var mqttClient in _clients)
  159. {
  160. try
  161. {
  162. mqttClient.DisconnectAsync().GetAwaiter().GetResult();
  163. }
  164. catch
  165. {
  166. // This can happen when the test already disconnected the client.
  167. }
  168. finally
  169. {
  170. mqttClient?.Dispose();
  171. }
  172. }
  173. try
  174. {
  175. Server?.StopAsync().GetAwaiter().GetResult();
  176. }
  177. catch
  178. {
  179. // This can happen when the test already stopped the server.
  180. }
  181. finally
  182. {
  183. Server?.Dispose();
  184. }
  185. ThrowIfLogErrors();
  186. if (_exceptions.Any())
  187. {
  188. throw new Exception($"{_exceptions.Count} exceptions tracked.\r\n" + string.Join(Environment.NewLine, _exceptions));
  189. }
  190. }
  191. public void TrackException(Exception exception)
  192. {
  193. lock (_exceptions)
  194. {
  195. _exceptions.Add(exception);
  196. }
  197. }
  198. }
  199. }