PageRenderTime 50ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/MongoDB.Driver.Core.Tests/Core/Clusters/ClusterTests.cs

http://github.com/mongodb/mongo-csharp-driver
C# | 619 lines | 509 code | 94 blank | 16 comment | 18 complexity | cb7d39333418676bd0be14c90eb1d037 MD5 | raw file
Possible License(s): Apache-2.0
  1. /* Copyright 2013-present MongoDB Inc.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Linq;
  18. using System.Net;
  19. using System.Reflection;
  20. using System.Threading;
  21. using System.Threading.Tasks;
  22. using FluentAssertions;
  23. using MongoDB.Bson.TestHelpers;
  24. using MongoDB.Bson.TestHelpers.XunitExtensions;
  25. using MongoDB.Driver.Core.Bindings;
  26. using MongoDB.Driver.Core.Clusters.ServerSelectors;
  27. using MongoDB.Driver.Core.Configuration;
  28. using MongoDB.Driver.Core.Events;
  29. using MongoDB.Driver.Core.Helpers;
  30. using MongoDB.Driver.Core.Misc;
  31. using MongoDB.Driver.Core.Servers;
  32. using Moq;
  33. using Xunit;
  34. namespace MongoDB.Driver.Core.Clusters
  35. {
  36. public class ClusterTests
  37. {
  38. private readonly EventCapturer _capturedEvents;
  39. private readonly Mock<IClusterableServerFactory> _mockServerFactory;
  40. private ClusterSettings _settings;
  41. public ClusterTests()
  42. {
  43. _settings = new ClusterSettings(serverSelectionTimeout: TimeSpan.FromSeconds(2));
  44. _mockServerFactory = new Mock<IClusterableServerFactory>();
  45. _mockServerFactory.Setup(f => f.CreateServer(It.IsAny<ClusterId>(), It.IsAny<IClusterClock>(), It.IsAny<EndPoint>()))
  46. .Returns((ClusterId clusterId, IClusterClock clusterClock, EndPoint endPoint) =>
  47. {
  48. var mockServer = new Mock<IClusterableServer>();
  49. mockServer.SetupGet(s => s.EndPoint).Returns(endPoint);
  50. return mockServer.Object;
  51. });
  52. _capturedEvents = new EventCapturer();
  53. }
  54. [Fact]
  55. public void SupportedWireVersionRange_should_return_expected_result()
  56. {
  57. var result = Cluster.SupportedWireVersionRange;
  58. result.Should().Be(new Range<int>(2, 9));
  59. }
  60. [Fact]
  61. public void Constructor_should_throw_if_settings_is_null()
  62. {
  63. Action act = () => new StubCluster(null, _mockServerFactory.Object, _capturedEvents);
  64. act.ShouldThrow<ArgumentNullException>();
  65. }
  66. [Fact]
  67. public void Constructor_should_throw_if_serverFactory_is_null()
  68. {
  69. Action act = () => new StubCluster(_settings, null, _capturedEvents);
  70. act.ShouldThrow<ArgumentNullException>();
  71. }
  72. [Fact]
  73. public void Constructor_should_throw_if_eventSubscriber_is_null()
  74. {
  75. Action act = () => new StubCluster(_settings, _mockServerFactory.Object, null);
  76. act.ShouldThrow<ArgumentNullException>();
  77. }
  78. [Theory]
  79. [InlineData(ClusterConnectionMode.Automatic, ClusterType.Unknown)]
  80. [InlineData(ClusterConnectionMode.Direct, ClusterType.Unknown)]
  81. [InlineData(ClusterConnectionMode.ReplicaSet, ClusterType.ReplicaSet)]
  82. [InlineData(ClusterConnectionMode.Sharded, ClusterType.Sharded)]
  83. [InlineData(ClusterConnectionMode.Standalone, ClusterType.Standalone)]
  84. public void Description_should_return_correct_description_when_not_initialized(ClusterConnectionMode connectionMode, ClusterType clusterType)
  85. {
  86. var subject = CreateSubject(connectionMode);
  87. var description = subject.Description;
  88. description.Servers.Should().BeEmpty();
  89. description.State.Should().Be(ClusterState.Disconnected);
  90. description.Type.Should().Be(ClusterType.Unknown);
  91. }
  92. [Fact]
  93. public void AcquireServerSession_should_call_serverSessionPool_AcquireSession()
  94. {
  95. var subject = CreateSubject();
  96. var mockServerSessionPool = new Mock<ICoreServerSessionPool>();
  97. var serverSessionPoolInfo = typeof(Cluster).GetField("_serverSessionPool", BindingFlags.NonPublic | BindingFlags.Instance);
  98. serverSessionPoolInfo.SetValue(subject, mockServerSessionPool.Object);
  99. var expectedResult = new Mock<ICoreServerSession>().Object;
  100. mockServerSessionPool.Setup(m => m.AcquireSession()).Returns(expectedResult);
  101. var result = subject.AcquireServerSession();
  102. result.Should().BeSameAs(expectedResult);
  103. mockServerSessionPool.Verify(m => m.AcquireSession(), Times.Once);
  104. }
  105. [Theory]
  106. [ParameterAttributeData]
  107. public void SelectServer_should_throw_if_not_initialized(
  108. [Values(false, true)]
  109. bool async)
  110. {
  111. var selector = new Mock<IServerSelector>().Object;
  112. var subject = CreateSubject();
  113. Action act;
  114. if (async)
  115. {
  116. act = () => subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  117. }
  118. else
  119. {
  120. act = () => subject.SelectServer(selector, CancellationToken.None);
  121. }
  122. act.ShouldThrow<InvalidOperationException>();
  123. }
  124. [Theory]
  125. [ParameterAttributeData]
  126. public void SelectServer_should_throw_if_disposed(
  127. [Values(false, true)]
  128. bool async)
  129. {
  130. var selector = new Mock<IServerSelector>().Object;
  131. var subject = CreateSubject();
  132. subject.Dispose();
  133. Action act;
  134. if (async)
  135. {
  136. act = () => subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  137. }
  138. else
  139. {
  140. act = () => subject.SelectServer(selector, CancellationToken.None);
  141. }
  142. act.ShouldThrow<ObjectDisposedException>();
  143. }
  144. [Theory]
  145. [ParameterAttributeData]
  146. public void SelectServer_should_throw_if_serverSelector_is_null(
  147. [Values(false, true)]
  148. bool async)
  149. {
  150. var subject = CreateSubject();
  151. subject.Initialize();
  152. Action act;
  153. if (async)
  154. {
  155. act = () => subject.SelectServerAsync(null, CancellationToken.None).GetAwaiter().GetResult();
  156. }
  157. else
  158. {
  159. act = () => subject.SelectServer(null, CancellationToken.None);
  160. }
  161. act.ShouldThrow<ArgumentNullException>();
  162. }
  163. [Theory]
  164. [ParameterAttributeData]
  165. public void SelectServer_should_return_a_server_if_one_matches(
  166. [Values(false, true)]
  167. bool async)
  168. {
  169. var subject = CreateSubject();
  170. subject.Initialize();
  171. var connected = ServerDescriptionHelper.Connected(subject.Description.ClusterId);
  172. subject.SetServerDescriptions(connected);
  173. _capturedEvents.Clear();
  174. var selector = new DelegateServerSelector((c, s) => s);
  175. IServer result;
  176. if (async)
  177. {
  178. result = subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  179. }
  180. else
  181. {
  182. result = subject.SelectServer(selector, CancellationToken.None);
  183. }
  184. result.Should().NotBeNull();
  185. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  186. _capturedEvents.Next().Should().BeOfType<ClusterSelectedServerEvent>();
  187. _capturedEvents.Any().Should().BeFalse();
  188. }
  189. [Theory]
  190. [ParameterAttributeData]
  191. public void SelectServer_should_return_second_server_if_first_cannot_be_found(
  192. [Values(false, true)]
  193. bool async)
  194. {
  195. var subject = CreateSubject();
  196. subject.Initialize();
  197. var endPoint1 = new DnsEndPoint("localhost", 27017);
  198. var endPoint2 = new DnsEndPoint("localhost", 27018);
  199. var connected1 = ServerDescriptionHelper.Connected(subject.Description.ClusterId, endPoint1);
  200. var connected2 = ServerDescriptionHelper.Connected(subject.Description.ClusterId, endPoint2);
  201. subject.SetServerDescriptions(connected1, connected2);
  202. subject.RemoveServer(endPoint1);
  203. _capturedEvents.Clear();
  204. var selector = new DelegateServerSelector((c, s) => s);
  205. IServer result;
  206. if (async)
  207. {
  208. result = subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  209. }
  210. else
  211. {
  212. result = subject.SelectServer(selector, CancellationToken.None);
  213. }
  214. result.Should().NotBeNull();
  215. result.EndPoint.Should().Be(endPoint2);
  216. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  217. _capturedEvents.Next().Should().BeOfType<ClusterSelectedServerEvent>();
  218. _capturedEvents.Any().Should().BeFalse();
  219. }
  220. [Theory]
  221. [ParameterAttributeData]
  222. public void SelectServer_should_throw_if_no_servers_match(
  223. [Values(false, true)]
  224. bool async)
  225. {
  226. var subject = CreateSubject();
  227. subject.Initialize();
  228. var connected = ServerDescriptionHelper.Connected(subject.Description.ClusterId);
  229. subject.SetServerDescriptions(connected);
  230. _capturedEvents.Clear();
  231. var selector = new DelegateServerSelector((c, s) => Enumerable.Empty<ServerDescription>());
  232. Action act;
  233. if (async)
  234. {
  235. act = () => subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  236. }
  237. else
  238. {
  239. act = () => subject.SelectServer(selector, CancellationToken.None);
  240. }
  241. act.ShouldThrow<TimeoutException>();
  242. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  243. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerFailedEvent>();
  244. _capturedEvents.Any().Should().BeFalse();
  245. }
  246. [Theory]
  247. [ParameterAttributeData]
  248. public void SelectServer_should_throw_if_the_matched_server_cannot_be_found_and_no_others_matched(
  249. [Values(false, true)]
  250. bool async)
  251. {
  252. var subject = CreateSubject(serverSelectionTimeout: TimeSpan.FromMilliseconds(10));
  253. subject.Initialize();
  254. var connected = ServerDescriptionHelper.Connected(subject.Description.ClusterId);
  255. subject.SetServerDescriptions(connected);
  256. subject.RemoveServer(connected.EndPoint);
  257. _capturedEvents.Clear();
  258. var selector = new DelegateServerSelector((c, s) => s);
  259. Action act;
  260. if (async)
  261. {
  262. act = () => subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  263. }
  264. else
  265. {
  266. act = () => subject.SelectServer(selector, CancellationToken.None);
  267. }
  268. act.ShouldThrow<TimeoutException>();
  269. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  270. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerFailedEvent>();
  271. _capturedEvents.Any().Should().BeFalse();
  272. }
  273. [Theory]
  274. [InlineData(0, 0, false)]
  275. [InlineData(0, 0, true)]
  276. [InlineData(10, 12, false)]
  277. [InlineData(10, 12, true)]
  278. public void SelectServer_should_throw_if_any_servers_are_incompatible(int min, int max, bool async)
  279. {
  280. var subject = CreateSubject();
  281. subject.Initialize();
  282. var connected = ServerDescriptionHelper.Connected(subject.Description.ClusterId, wireVersionRange: new Range<int>(min, max));
  283. subject.SetServerDescriptions(connected);
  284. _capturedEvents.Clear();
  285. var selector = new DelegateServerSelector((c, s) => s);
  286. Action act;
  287. if (async)
  288. {
  289. act = () => subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  290. }
  291. else
  292. {
  293. act = () => subject.SelectServer(selector, CancellationToken.None);
  294. }
  295. act.ShouldThrow<MongoIncompatibleDriverException>();
  296. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  297. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerFailedEvent>();
  298. _capturedEvents.Any().Should().BeFalse();
  299. }
  300. [Theory]
  301. [ParameterAttributeData]
  302. public void SelectServer_should_keep_trying_to_match_by_waiting_on_cluster_description_changes(
  303. [Values(false, true)]
  304. bool async)
  305. {
  306. var subject = CreateSubject();
  307. subject.Initialize();
  308. var connecting = ServerDescriptionHelper.Disconnected(subject.Description.ClusterId);
  309. var connected = ServerDescriptionHelper.Connected(subject.Description.ClusterId);
  310. subject.SetServerDescriptions(connecting);
  311. _capturedEvents.Clear();
  312. Task.Run(() =>
  313. {
  314. var descriptions = new Queue<ServerDescription>(new[] { connecting, connecting, connecting, connected });
  315. while (descriptions.Count > 0)
  316. {
  317. Thread.Sleep(TimeSpan.FromMilliseconds(20));
  318. var next = descriptions.Dequeue();
  319. subject.SetServerDescriptions(next);
  320. }
  321. });
  322. var selector = new DelegateServerSelector((c, s) => s);
  323. IServer result;
  324. if (async)
  325. {
  326. result = subject.SelectServerAsync(selector, CancellationToken.None).GetAwaiter().GetResult();
  327. }
  328. else
  329. {
  330. result = subject.SelectServer(selector, CancellationToken.None);
  331. }
  332. result.Should().NotBeNull();
  333. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  334. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  335. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  336. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  337. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  338. _capturedEvents.Next().Should().BeOfType<ClusterSelectedServerEvent>();
  339. _capturedEvents.Any().Should().BeFalse();
  340. }
  341. [Fact]
  342. public void StartSession_should_return_expected_result()
  343. {
  344. var subject = CreateSubject();
  345. var options = new CoreSessionOptions();
  346. var result = subject.StartSession(options);
  347. result.Options.Should().BeSameAs(options);
  348. result.ServerSession.Should().NotBeNull();
  349. }
  350. [Fact]
  351. public void DescriptionChanged_should_be_raised_when_the_description_changes()
  352. {
  353. int count = 0;
  354. var subject = CreateSubject();
  355. subject.Initialize();
  356. subject.DescriptionChanged += (o, e) => count++;
  357. subject.SetServerDescriptions(ServerDescriptionHelper.Connected(subject.Description.ClusterId));
  358. subject.SetServerDescriptions(ServerDescriptionHelper.Connected(subject.Description.ClusterId, averageRoundTripTime: TimeSpan.FromMilliseconds(10)));
  359. subject.SetServerDescriptions(ServerDescriptionHelper.Connected(subject.Description.ClusterId, averageRoundTripTime: TimeSpan.FromMilliseconds(13)));
  360. count.Should().Be(3);
  361. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  362. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  363. _capturedEvents.Next().Should().BeOfType<ClusterDescriptionChangedEvent>();
  364. _capturedEvents.Any().Should().BeFalse();
  365. }
  366. [Theory]
  367. [ParameterAttributeData]
  368. public void SelectServer_should_apply_both_pre_and_post_server_selectors(
  369. [Values(false, true)]
  370. bool async)
  371. {
  372. _mockServerFactory.Setup(f => f.CreateServer(It.IsAny<ClusterId>(), It.IsAny<IClusterClock>(), It.IsAny<EndPoint>()))
  373. .Returns((ClusterId _, IClusterClock clusterClock, EndPoint endPoint) =>
  374. {
  375. var mockServer = new Mock<IClusterableServer>();
  376. mockServer.SetupGet(s => s.EndPoint).Returns(endPoint);
  377. return mockServer.Object;
  378. });
  379. var preSelector = new DelegateServerSelector((cd, sds) => sds.Where(x => ((DnsEndPoint)x.EndPoint).Port != 27017));
  380. var middleSelector = new DelegateServerSelector((cd, sds) => sds.Where(x => ((DnsEndPoint)x.EndPoint).Port != 27018));
  381. var postSelector = new DelegateServerSelector((cd, sds) => sds.Where(x => ((DnsEndPoint)x.EndPoint).Port != 27019));
  382. var settings = new ClusterSettings(
  383. preServerSelector: preSelector,
  384. postServerSelector: postSelector);
  385. var subject = new StubCluster(settings, _mockServerFactory.Object, _capturedEvents);
  386. subject.Initialize();
  387. subject.SetServerDescriptions(
  388. ServerDescriptionHelper.Connected(subject.Description.ClusterId, new DnsEndPoint("localhost", 27017)),
  389. ServerDescriptionHelper.Connected(subject.Description.ClusterId, new DnsEndPoint("localhost", 27018)),
  390. ServerDescriptionHelper.Connected(subject.Description.ClusterId, new DnsEndPoint("localhost", 27019)),
  391. ServerDescriptionHelper.Connected(subject.Description.ClusterId, new DnsEndPoint("localhost", 27020)));
  392. _capturedEvents.Clear();
  393. IServer result;
  394. if (async)
  395. {
  396. result = subject.SelectServerAsync(middleSelector, CancellationToken.None).GetAwaiter().GetResult();
  397. }
  398. else
  399. {
  400. result = subject.SelectServer(middleSelector, CancellationToken.None);
  401. }
  402. ((DnsEndPoint)result.EndPoint).Port.Should().Be(27020);
  403. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  404. _capturedEvents.Next().Should().BeOfType<ClusterSelectedServerEvent>();
  405. _capturedEvents.Any().Should().BeFalse();
  406. }
  407. [Theory]
  408. [ParameterAttributeData]
  409. public void SelectServer_should_call_custom_selector(
  410. [Values(true, false)] bool withEligibleServers,
  411. [Values(true, false)] bool async)
  412. {
  413. int numberOfCustomServerSelectorCalls = 0;
  414. var customServerSelector = new DelegateServerSelector((c, s) =>
  415. {
  416. numberOfCustomServerSelectorCalls++;
  417. return s.Skip(1);
  418. });
  419. var settings = _settings.With(postServerSelector: customServerSelector);
  420. var subject = new StubCluster(settings, _mockServerFactory.Object, _capturedEvents);
  421. subject.Initialize();
  422. subject.SetServerDescriptions(
  423. ServerDescriptionHelper.Connected(subject.Description.ClusterId, new DnsEndPoint("localhost", 27019)),
  424. ServerDescriptionHelper.Connected(subject.Description.ClusterId, new DnsEndPoint("localhost", 27020)));
  425. _capturedEvents.Clear();
  426. if (withEligibleServers)
  427. {
  428. var selectedServer = SelectServerAttempt(
  429. subject,
  430. new DelegateServerSelector((c, s) => s), // do not filter servers
  431. async);
  432. var selectedServerPort = ((DnsEndPoint)selectedServer.EndPoint).Port;
  433. selectedServerPort.Should().Be(27020);
  434. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  435. _capturedEvents.Next().Should().BeOfType<ClusterSelectedServerEvent>();
  436. }
  437. else
  438. {
  439. var exception = Record.Exception(
  440. () =>
  441. SelectServerAttempt(
  442. subject,
  443. new DelegateServerSelector((c, s) => new ServerDescription[0]), // no eligible servers
  444. async));
  445. exception.Should().BeOfType<TimeoutException>();
  446. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerEvent>();
  447. _capturedEvents.Next().Should().BeOfType<ClusterSelectingServerFailedEvent>();
  448. }
  449. numberOfCustomServerSelectorCalls.Should().Be(1);
  450. _capturedEvents.Any().Should().BeFalse();
  451. }
  452. // private methods
  453. private StubCluster CreateSubject(ClusterConnectionMode connectionMode = ClusterConnectionMode.Automatic, TimeSpan? serverSelectionTimeout = null)
  454. {
  455. _settings = _settings.With(connectionMode: connectionMode);
  456. if (serverSelectionTimeout != null)
  457. {
  458. _settings = _settings.With(serverSelectionTimeout: serverSelectionTimeout.Value);
  459. }
  460. return new StubCluster(_settings, _mockServerFactory.Object, _capturedEvents);
  461. }
  462. private IServer SelectServerAttempt(Cluster cluster, IServerSelector operationSelector, bool async)
  463. {
  464. if (async)
  465. {
  466. return cluster
  467. .SelectServerAsync(operationSelector, CancellationToken.None)
  468. .GetAwaiter()
  469. .GetResult();
  470. }
  471. else
  472. {
  473. return cluster.SelectServer(operationSelector, CancellationToken.None);
  474. }
  475. }
  476. // nested types
  477. private class StubCluster : Cluster
  478. {
  479. private Dictionary<EndPoint, IClusterableServer> _servers = new Dictionary<EndPoint, IClusterableServer>();
  480. public StubCluster(ClusterSettings settings, IClusterableServerFactory serverFactory, IEventSubscriber eventSubscriber)
  481. : base(settings, serverFactory, eventSubscriber)
  482. {
  483. }
  484. public override void Initialize()
  485. {
  486. base.Initialize();
  487. }
  488. public void RemoveServer(EndPoint endPoint)
  489. {
  490. _servers.Remove(endPoint);
  491. }
  492. public void SetServerDescriptions(params ServerDescription[] serverDescriptions)
  493. {
  494. var description = serverDescriptions.Aggregate(Description, (d, s) => d.WithServerDescription(s));
  495. UpdateClusterDescription(description);
  496. AddOrRemoveServers(description);
  497. }
  498. protected override void RequestHeartbeat()
  499. {
  500. }
  501. protected override bool TryGetServer(EndPoint endPoint, out IClusterableServer server)
  502. {
  503. return _servers.TryGetValue(endPoint, out server);
  504. }
  505. private void AddOrRemoveServers(ClusterDescription clusterDescription)
  506. {
  507. var endPoints = clusterDescription.Servers.Select(s => s.EndPoint).ToList();
  508. var endPointsToAdd = endPoints.Where(e => !_servers.ContainsKey(e));
  509. var endPointsToRemove = _servers.Keys.Where(e => !endPoints.Contains(e));
  510. foreach (var endPoint in endPointsToAdd)
  511. {
  512. _servers.Add(endPoint, CreateServer(endPoint));
  513. }
  514. foreach (var endPoint in endPointsToRemove)
  515. {
  516. _servers.Remove(endPoint);
  517. }
  518. }
  519. }
  520. }
  521. internal static class ClusterReflector
  522. {
  523. public static InterlockedInt32 _state(this Cluster cluster) => (InterlockedInt32)Reflector.GetFieldValue(cluster, nameof(_state));
  524. }
  525. }