PageRenderTime 57ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/MongoDB.Driver.Tests/Specifications/command-monitoring/TestRunner.cs

http://github.com/mongodb/mongo-csharp-driver
C# | 376 lines | 316 code | 39 blank | 21 comment | 27 complexity | c4137c57ba8dec912ef1e256243ddef1 MD5 | raw file
Possible License(s): Apache-2.0
  1. /* Copyright 2010-2016 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.IO;
  18. using System.Linq;
  19. using System.Reflection;
  20. using System.Threading;
  21. using System.Threading.Tasks;
  22. using FluentAssertions;
  23. using MongoDB.Bson;
  24. using MongoDB.Driver.Core;
  25. using MongoDB.Driver.Core.Clusters.ServerSelectors;
  26. using MongoDB.Driver.Core.Events;
  27. using MongoDB.Driver.Core.Misc;
  28. using MongoDB.Bson.TestHelpers.XunitExtensions;
  29. using Xunit;
  30. using System.Collections;
  31. namespace MongoDB.Driver.Tests.Specifications.command_monitoring
  32. {
  33. public class TestRunner
  34. {
  35. private static MongoClient __client;
  36. private static EventCapturer __capturedEvents;
  37. private static Dictionary<string, Func<ICrudOperationTest>> __tests;
  38. private static string[] __commandsToCapture;
  39. private static bool __oneTimeSetupHasRun = false;
  40. private static object __oneTimeSetupLock = new object();
  41. static TestRunner()
  42. {
  43. __commandsToCapture = new string[]
  44. {
  45. "delete",
  46. "insert",
  47. "update",
  48. "find",
  49. "count",
  50. "killCursors",
  51. "getMore"
  52. };
  53. __tests = new Dictionary<string, Func<ICrudOperationTest>>
  54. {
  55. { "bulkWrite", () => new BulkWriteTest() },
  56. { "count", () => new CountTest() },
  57. { "deleteMany", () => new DeleteManyTest() },
  58. { "deleteOne", () => new DeleteOneTest() },
  59. { "find", () => new FindTest() },
  60. { "insertMany", () => new InsertManyTest() },
  61. { "insertOne", () => new InsertOneTest() },
  62. { "updateMany", () => new UpdateManyTest() },
  63. { "updateOne", () => new UpdateOneTest() },
  64. };
  65. }
  66. public TestRunner()
  67. {
  68. lock (__oneTimeSetupLock)
  69. {
  70. __oneTimeSetupHasRun = __oneTimeSetupHasRun || OneTimeSetup();
  71. }
  72. }
  73. public bool OneTimeSetup()
  74. {
  75. __capturedEvents = new EventCapturer()
  76. .Capture<CommandStartedEvent>(e => __commandsToCapture.Contains(e.CommandName))
  77. .Capture<CommandSucceededEvent>(e => __commandsToCapture.Contains(e.CommandName))
  78. .Capture<CommandFailedEvent>(e => __commandsToCapture.Contains(e.CommandName));
  79. var settings = new MongoClientSettings
  80. {
  81. ClusterConfigurator = cb =>
  82. {
  83. cb = CoreTestConfiguration.ConfigureCluster(cb);
  84. cb.Subscribe(__capturedEvents);
  85. // never heartbeat...
  86. cb.ConfigureServer(ss => ss.With(heartbeatInterval: Timeout.InfiniteTimeSpan));
  87. }
  88. };
  89. __client = new MongoClient(settings);
  90. return true;
  91. }
  92. [SkippableTheory]
  93. [ClassData(typeof(TestCaseFactory))]
  94. public void RunTestDefinition(IEnumerable<BsonDocument> data, string databaseName, string collectionName, BsonDocument definition, bool async)
  95. {
  96. definition = (BsonDocument)DeepCopy(definition); // protect against side effects when the same definition is run twice (async=false/true)
  97. BsonValue bsonValue;
  98. if (definition.TryGetValue("ignore_if_server_version_greater_than", out bsonValue))
  99. {
  100. var serverVersion = GetServerVersion();
  101. var maxServerVersion = SemanticVersion.Parse(bsonValue.AsString);
  102. if (serverVersion > maxServerVersion)
  103. {
  104. throw new SkipTestException($"Test ignored because server version {serverVersion} is greater than max server version {maxServerVersion}.");
  105. }
  106. }
  107. if (definition.TryGetValue("ignore_if_server_version_less_than", out bsonValue))
  108. {
  109. var serverVersion = GetServerVersion();
  110. var minServerVersion = SemanticVersion.Parse(bsonValue.AsString);
  111. if (serverVersion < minServerVersion)
  112. {
  113. throw new SkipTestException($"Test ignored because server version {serverVersion} is less than min server version {minServerVersion}.");
  114. }
  115. }
  116. var database = __client
  117. .GetDatabase(databaseName);
  118. var collection = database
  119. .GetCollection<BsonDocument>(collectionName);
  120. database.DropCollection(collection.CollectionNamespace.CollectionName);
  121. collection.InsertMany(data);
  122. __capturedEvents.Clear();
  123. try
  124. {
  125. ExecuteOperation(database, collection, (BsonDocument)definition["operation"], async);
  126. }
  127. catch (NotImplementedException)
  128. {
  129. throw;
  130. }
  131. catch (Exception)
  132. {
  133. // catch everything...
  134. }
  135. long? operationId = null;
  136. foreach (BsonDocument expected in (BsonArray)definition["expectations"])
  137. {
  138. if (!__capturedEvents.Any() && !SpinWait.SpinUntil(__capturedEvents.Any, TimeSpan.FromSeconds(5)))
  139. {
  140. Assert.True(false, "Expected an event, but no events were captured.");
  141. }
  142. if (expected.Contains("command_started_event"))
  143. {
  144. var actual = (CommandStartedEvent)__capturedEvents.Next();
  145. if (!operationId.HasValue)
  146. {
  147. operationId = actual.OperationId;
  148. }
  149. actual.OperationId.Should().Be(operationId);
  150. VerifyCommandStartedEvent(actual, (BsonDocument)expected["command_started_event"], databaseName, collectionName);
  151. }
  152. else if (expected.Contains("command_succeeded_event"))
  153. {
  154. var actual = (CommandSucceededEvent)__capturedEvents.Next();
  155. actual.OperationId.Should().Be(operationId);
  156. VerifyCommandSucceededEvent(actual, (BsonDocument)expected["command_succeeded_event"], databaseName, collectionName);
  157. }
  158. else if (expected.Contains("command_failed_event"))
  159. {
  160. var actual = (CommandFailedEvent)__capturedEvents.Next();
  161. actual.OperationId.Should().Be(operationId);
  162. VerifyCommandFailedEvent(actual, (BsonDocument)expected["command_failed_event"], databaseName, collectionName);
  163. }
  164. else
  165. {
  166. Assert.True(false, "Unknown event type.");
  167. }
  168. }
  169. }
  170. private SemanticVersion GetServerVersion()
  171. {
  172. var server = __client.Cluster.SelectServer(WritableServerSelector.Instance, CancellationToken.None);
  173. return server.Description.Version;
  174. }
  175. private void ExecuteOperation(IMongoDatabase database, IMongoCollection<BsonDocument> collection, BsonDocument operation, bool async)
  176. {
  177. var name = (string)operation["name"];
  178. Func<ICrudOperationTest> factory;
  179. if (!__tests.TryGetValue(name, out factory))
  180. {
  181. throw new NotImplementedException("The operation " + name + " has not been implemented.");
  182. }
  183. var arguments = (BsonDocument)operation.GetValue("arguments", new BsonDocument());
  184. var test = factory();
  185. string reason;
  186. if (!test.CanExecute(__client.Cluster.Description, arguments, out reason))
  187. {
  188. throw new SkipTestException(reason);
  189. }
  190. test.Execute(__client.Cluster.Description, database, collection, arguments, async);
  191. }
  192. private void VerifyCommandStartedEvent(CommandStartedEvent actual, BsonDocument expected, string databaseName, string collectionName)
  193. {
  194. actual.CommandName.Should().Be(expected["command_name"].ToString());
  195. actual.DatabaseNamespace.Should().Be(new DatabaseNamespace(databaseName));
  196. var command = MassageCommand(actual.CommandName, actual.Command);
  197. command.Should().BeEquivalentTo((BsonDocument)expected["command"]);
  198. }
  199. private void VerifyCommandSucceededEvent(CommandSucceededEvent actual, BsonDocument expected, string databaseName, string collectionName)
  200. {
  201. actual.CommandName.Should().Be(expected["command_name"].ToString());
  202. var expectedReply = (BsonDocument)expected["reply"];
  203. var reply = MassageReply(actual.CommandName, actual.Reply, expectedReply);
  204. reply.Should().BeEquivalentTo(expectedReply);
  205. }
  206. private void VerifyCommandFailedEvent(CommandFailedEvent actual, BsonDocument expected, string databaseName, string collectionName)
  207. {
  208. actual.CommandName.Should().Be(expected["command_name"].ToString());
  209. }
  210. private BsonDocument MassageCommand(string commandName, BsonDocument command)
  211. {
  212. var massagedCommand = (BsonDocument)DeepCopy(command);
  213. switch (commandName)
  214. {
  215. case "delete":
  216. massagedCommand["ordered"] = massagedCommand.GetValue("ordered", true);
  217. break;
  218. case "getMore":
  219. massagedCommand["getMore"] = 42L;
  220. break;
  221. case "insert":
  222. massagedCommand["ordered"] = massagedCommand.GetValue("ordered", true);
  223. break;
  224. case "killCursors":
  225. massagedCommand["cursors"][0] = 42L;
  226. break;
  227. case "update":
  228. massagedCommand["ordered"] = massagedCommand.GetValue("ordered", true);
  229. foreach (BsonDocument update in (BsonArray)massagedCommand["updates"])
  230. {
  231. update["multi"] = update.GetValue("multi", false);
  232. update["upsert"] = update.GetValue("upsert", false);
  233. }
  234. break;
  235. }
  236. return massagedCommand;
  237. }
  238. private BsonDocument MassageReply(string commandName, BsonDocument reply, BsonDocument expectedReply)
  239. {
  240. var massagedReply = (BsonDocument)DeepCopy(reply);
  241. switch (commandName)
  242. {
  243. case "find":
  244. case "getMore":
  245. if (massagedReply.Contains("cursor") && massagedReply["cursor"]["id"] != 0L)
  246. {
  247. massagedReply["cursor"]["id"] = 42L;
  248. }
  249. break;
  250. case "killCursors":
  251. massagedReply["cursorsUnknown"][0] = 42L;
  252. break;
  253. case "delete":
  254. case "insert":
  255. case "update":
  256. if (massagedReply.Contains("writeErrors"))
  257. {
  258. foreach (BsonDocument writeError in (BsonArray)massagedReply["writeErrors"])
  259. {
  260. writeError["code"] = 42;
  261. writeError["errmsg"] = "";
  262. }
  263. }
  264. break;
  265. }
  266. // add any fields in the actual reply into the expected reply that don't already exist
  267. expectedReply.Merge(reply, false);
  268. return massagedReply;
  269. }
  270. private BsonValue DeepCopy(BsonValue value)
  271. {
  272. if (value.BsonType == BsonType.Document)
  273. {
  274. var document = new BsonDocument();
  275. foreach (var element in (BsonDocument)value)
  276. {
  277. document.Add(element.Name, DeepCopy(element.Value));
  278. }
  279. return document;
  280. }
  281. else if (value.BsonType == BsonType.Array)
  282. {
  283. var array = new BsonArray();
  284. foreach (var element in (BsonArray)value)
  285. {
  286. array.Add(DeepCopy(element));
  287. }
  288. return array;
  289. }
  290. return value;
  291. }
  292. private class TestCaseFactory : IEnumerable<object[]>
  293. {
  294. public IEnumerator<object[]> GetEnumerator()
  295. {
  296. const string prefix = "MongoDB.Driver.Tests.Specifications.command_monitoring.tests.";
  297. var testDocuments = typeof(TestCaseFactory).GetTypeInfo().Assembly
  298. .GetManifestResourceNames()
  299. .Where(path => path.StartsWith(prefix) && path.EndsWith(".json"))
  300. .Select(path => ReadDocument(path));
  301. var testCases = new List<object[]>();
  302. foreach (var testDocument in testDocuments)
  303. {
  304. var data = testDocument["data"].AsBsonArray.Cast<BsonDocument>().ToList();
  305. var databaseName = testDocument["database_name"].ToString();
  306. var collectionName = testDocument["collection_name"].ToString();
  307. foreach (BsonDocument definition in testDocument["tests"].AsBsonArray)
  308. {
  309. foreach (var async in new[] { false, true })
  310. {
  311. //var testCase = new TestCaseData(data, databaseName, collectionName, definition, async);
  312. //testCase.SetCategory("Specifications");
  313. //testCase.SetCategory("command-monitoring");
  314. //testCase.SetName($"{definition["description"]}({async})");
  315. var testCase = new object[] { data, databaseName, collectionName, definition, async };
  316. testCases.Add(testCase);
  317. }
  318. }
  319. }
  320. return testCases.GetEnumerator();
  321. }
  322. IEnumerator IEnumerable.GetEnumerator()
  323. {
  324. return GetEnumerator();
  325. }
  326. private static BsonDocument ReadDocument(string path)
  327. {
  328. using (var definitionStream = typeof(TestCaseFactory).GetTypeInfo().Assembly.GetManifestResourceStream(path))
  329. using (var definitionStringReader = new StreamReader(definitionStream))
  330. {
  331. var definitionString = definitionStringReader.ReadToEnd();
  332. return BsonDocument.Parse(definitionString);
  333. }
  334. }
  335. }
  336. }
  337. }