1 var MongoRunner, _startMongod, startMongoProgram, runMongoProgram, startMongoProgramNoConnect,
  2     myPort;
  3 
  4 (function() {
  5     "use strict";
  6 
  7     var shellVersion = version;
  8 
  9     var _parsePath = function() {
 10         var dbpath = "";
 11         for (var i = 0; i < arguments.length; ++i)
 12             if (arguments[i] == "--dbpath")
 13                 dbpath = arguments[i + 1];
 14 
 15         if (dbpath == "")
 16             throw Error("No dbpath specified");
 17 
 18         return dbpath;
 19     };
 20 
 21     var _parsePort = function() {
 22         var port = "";
 23         for (var i = 0; i < arguments.length; ++i)
 24             if (arguments[i] == "--port")
 25                 port = arguments[i + 1];
 26 
 27         if (port == "")
 28             throw Error("No port specified");
 29         return port;
 30     };
 31 
 32     var createMongoArgs = function(binaryName, args) {
 33         if (!Array.isArray(args)) {
 34             throw new Error("The second argument to createMongoArgs must be an array");
 35         }
 36 
 37         var fullArgs = [binaryName];
 38 
 39         if (args.length == 1 && isObject(args[0])) {
 40             var o = args[0];
 41             for (var k in o) {
 42                 if (o.hasOwnProperty(k)) {
 43                     if (k == "v" && isNumber(o[k])) {
 44                         var n = o[k];
 45                         if (n > 0) {
 46                             if (n > 10)
 47                                 n = 10;
 48                             var temp = "-";
 49                             while (n-- > 0)
 50                                 temp += "v";
 51                             fullArgs.push(temp);
 52                         }
 53                     } else {
 54                         fullArgs.push("--" + k);
 55                         if (o[k] != "")
 56                             fullArgs.push("" + o[k]);
 57                     }
 58                 }
 59             }
 60         } else {
 61             for (var i = 0; i < args.length; i++)
 62                 fullArgs.push(args[i]);
 63         }
 64 
 65         return fullArgs;
 66     };
 67 
 68     MongoRunner = function() {};
 69 
 70     MongoRunner.dataDir = "/data/db";
 71     MongoRunner.dataPath = "/data/db/";
 72 
 73     MongoRunner.VersionSub = function(pattern, version) {
 74         this.pattern = pattern;
 75         this.version = version;
 76     };
 77 
 78     /**
 79      * Returns an array of version elements from a version string.
 80      *
 81      * "3.3.4-fade3783" -> ["3", "3", "4-fade3783" ]
 82      * "3.2" -> [ "3", "2" ]
 83      * 3 -> exception: versions must have at least two components.
 84      */
 85     var convertVersionStringToArray = function(versionString) {
 86         assert("" !== versionString, "Version strings must not be empty");
 87         var versionArray = versionString.split('.');
 88         assert.gt(versionArray.length,
 89                   1,
 90                   "MongoDB versions must have at least two components to compare, but \"" +
 91                       versionString + "\" has " + versionArray.length);
 92         return versionArray;
 93     };
 94 
 95     /**
 96      * Returns the major version string from a version string.
 97      *
 98      * 3.3.4-fade3783 -> 3.3
 99      * 3.2 -> 3.2
100      * 3 -> exception: versions must have at least two components.
101      */
102     var extractMajorVersionFromVersionString = function(versionString) {
103         return convertVersionStringToArray(versionString).slice(0, 2).join('.');
104     };
105 
106     // These patterns allow substituting the binary versions used for each version string to support
107     // the
108     // dev/stable MongoDB release cycle.
109     //
110     // If you add a new version substitution to this list, you should add it to the lists of
111     // versions being checked in 'verify_versions_test.js' to verify it is susbstituted correctly.
112     MongoRunner.binVersionSubs = [
113         new MongoRunner.VersionSub("latest", shellVersion()),
114         new MongoRunner.VersionSub(extractMajorVersionFromVersionString(shellVersion()),
115                                    shellVersion()),
116         // To-be-updated when we branch for the next release.
117         new MongoRunner.VersionSub("last-stable", "3.2")
118     ];
119 
120     MongoRunner.getBinVersionFor = function(version) {
121 
122         // If this is a version iterator, iterate the version via toString()
123         if (version instanceof MongoRunner.versionIterator.iterator) {
124             version = version.toString();
125         }
126 
127         if (version == null)
128             version = "";
129         version = version.trim();
130         if (version === "")
131             version = "latest";
132 
133         // See if this version is affected by version substitutions
134         for (var i = 0; i < MongoRunner.binVersionSubs.length; i++) {
135             var sub = MongoRunner.binVersionSubs[i];
136             if (sub.pattern == version) {
137                 return sub.version;
138             }
139         }
140 
141         return version;
142     };
143 
144     /**
145      * Returns true if two version strings could represent the same version. This is true
146      * if, after passing the versions through getBinVersionFor, the the versions have the
147      * same value for each version component up through the length of the shorter version.
148      *
149      * That is, 3.2.4 compares equal to 3.2, but 3.2.4 does not compare equal to 3.2.3.
150      */
151     MongoRunner.areBinVersionsTheSame = function(versionA, versionB) {
152 
153         versionA = convertVersionStringToArray(MongoRunner.getBinVersionFor(versionA));
154         versionB = convertVersionStringToArray(MongoRunner.getBinVersionFor(versionB));
155 
156         var elementsToCompare = Math.min(versionA.length, versionB.length);
157         for (var i = 0; i < elementsToCompare; ++i) {
158             if (versionA[i] != versionB[i]) {
159                 return false;
160             }
161         }
162         return true;
163     };
164 
165     MongoRunner.logicalOptions = {
166         runId: true,
167         env: true,
168         pathOpts: true,
169         remember: true,
170         noRemember: true,
171         appendOptions: true,
172         restart: true,
173         noCleanData: true,
174         cleanData: true,
175         startClean: true,
176         forceLock: true,
177         useLogFiles: true,
178         logFile: true,
179         useHostName: true,
180         useHostname: true,
181         noReplSet: true,
182         forgetPort: true,
183         arbiter: true,
184         noJournalPrealloc: true,
185         noJournal: true,
186         binVersion: true,
187         waitForConnect: true,
188         bridgeOptions: true
189     };
190 
191     MongoRunner.toRealPath = function(path, pathOpts) {
192 
193         // Replace all $pathOptions with actual values
194         pathOpts = pathOpts || {};
195         path = path.replace(/\$dataPath/g, MongoRunner.dataPath);
196         path = path.replace(/\$dataDir/g, MongoRunner.dataDir);
197         for (var key in pathOpts) {
198             path = path.replace(RegExp("\\$" + RegExp.escape(key), "g"), pathOpts[key]);
199         }
200 
201         // Relative path
202         // Detect Unix and Windows absolute paths
203         // as well as Windows drive letters
204         // Also captures Windows UNC paths
205 
206         if (!path.match(/^(\/|\\|[A-Za-z]:)/)) {
207             if (path != "" && !path.endsWith("/"))
208                 path += "/";
209 
210             path = MongoRunner.dataPath + path;
211         }
212 
213         return path;
214 
215     };
216 
217     MongoRunner.toRealDir = function(path, pathOpts) {
218 
219         path = MongoRunner.toRealPath(path, pathOpts);
220 
221         if (path.endsWith("/"))
222             path = path.substring(0, path.length - 1);
223 
224         return path;
225     };
226 
227     MongoRunner.toRealFile = MongoRunner.toRealDir;
228 
229     /**
230      * Returns an iterator object which yields successive versions on toString(), starting from a
231      * random initial position, from an array of versions.
232      *
233      * If passed a single version string or an already-existing version iterator, just returns the
234      * object itself, since it will yield correctly on toString()
235      *
236      * @param {Array.<String>}|{String}|{versionIterator}
237      */
238     MongoRunner.versionIterator = function(arr, isRandom) {
239 
240         // If this isn't an array of versions, or is already an iterator, just use it
241         if (typeof arr == "string")
242             return arr;
243         if (arr.isVersionIterator)
244             return arr;
245 
246         if (isRandom == undefined)
247             isRandom = false;
248 
249         // Starting pos
250         var i = isRandom ? parseInt(Random.rand() * arr.length) : 0;
251 
252         return new MongoRunner.versionIterator.iterator(i, arr);
253     };
254 
255     MongoRunner.versionIterator.iterator = function(i, arr) {
256 
257         this.toString = function() {
258             i = i % arr.length;
259             print("Returning next version : " + i + " (" + arr[i] + ") from " + tojson(arr) +
260                   "...");
261             return arr[i++];
262         };
263 
264         this.isVersionIterator = true;
265 
266     };
267 
268     /**
269      * Converts the args object by pairing all keys with their value and appending
270      * dash-dash (--) to the keys. The only exception to this rule are keys that
271      * are defined in MongoRunner.logicalOptions, of which they will be ignored.
272      *
273      * @param {string} binaryName
274      * @param {Object} args
275      *
276      * @return {Array.<String>} an array of parameter strings that can be passed
277      *   to the binary.
278      */
279     MongoRunner.arrOptions = function(binaryName, args) {
280 
281         var fullArgs = [""];
282 
283         // isObject returns true even if "args" is an array, so the else branch of this statement is
284         // dead code.  See SERVER-14220.
285         if (isObject(args) || (args.length == 1 && isObject(args[0]))) {
286             var o = isObject(args) ? args : args[0];
287 
288             // If we've specified a particular binary version, use that
289             if (o.binVersion && o.binVersion != "" && o.binVersion != shellVersion()) {
290                 binaryName += "-" + o.binVersion;
291             }
292 
293             // Manage legacy options
294             var isValidOptionForBinary = function(option, value) {
295 
296                 if (!o.binVersion)
297                     return true;
298 
299                 // Version 1.x options
300                 if (o.binVersion.startsWith("1.")) {
301                     return ["nopreallocj"].indexOf(option) < 0;
302                 }
303 
304                 return true;
305             };
306 
307             var addOptionsToFullArgs = function(k, v) {
308                 if (v === undefined || v === null)
309                     return;
310 
311                 fullArgs.push("--" + k);
312 
313                 if (v != "") {
314                     fullArgs.push("" + v);
315                 }
316             };
317 
318             for (var k in o) {
319                 // Make sure our logical option should be added to the array of options
320                 if (!o.hasOwnProperty(k) || k in MongoRunner.logicalOptions ||
321                     !isValidOptionForBinary(k, o[k]))
322                     continue;
323 
324                 if ((k == "v" || k == "verbose") && isNumber(o[k])) {
325                     var n = o[k];
326                     if (n > 0) {
327                         if (n > 10)
328                             n = 10;
329                         var temp = "-";
330                         while (n-- > 0)
331                             temp += "v";
332                         fullArgs.push(temp);
333                     }
334                 } else if (k === "setParameter" && isObject(o[k])) {
335                     // If the value associated with the setParameter option is an object, we want
336                     // to add all key-value pairs in that object as separate --setParameters.
337                     Object.keys(o[k]).forEach(function(paramKey) {
338                         addOptionsToFullArgs(k, "" + paramKey + "=" + o[k][paramKey]);
339                     });
340                 } else {
341                     addOptionsToFullArgs(k, o[k]);
342                 }
343             }
344         } else {
345             for (var i = 0; i < args.length; i++)
346                 fullArgs.push(args[i]);
347         }
348 
349         fullArgs[0] = binaryName;
350         return fullArgs;
351     };
352 
353     MongoRunner.arrToOpts = function(arr) {
354 
355         var opts = {};
356         for (var i = 1; i < arr.length; i++) {
357             if (arr[i].startsWith("-")) {
358                 var opt = arr[i].replace(/^-/, "").replace(/^-/, "");
359 
360                 if (arr.length > i + 1 && !arr[i + 1].startsWith("-")) {
361                     opts[opt] = arr[i + 1];
362                     i++;
363                 } else {
364                     opts[opt] = "";
365                 }
366 
367                 if (opt.replace(/v/g, "") == "") {
368                     opts["verbose"] = opt.length;
369                 }
370             }
371         }
372 
373         return opts;
374     };
375 
376     MongoRunner.savedOptions = {};
377 
378     MongoRunner.mongoOptions = function(opts) {
379         // Don't remember waitForConnect
380         var waitForConnect = opts.waitForConnect;
381         delete opts.waitForConnect;
382 
383         // If we're a mongo object
384         if (opts.getDB) {
385             opts = {restart: opts.runId};
386         }
387 
388         // Initialize and create a copy of the opts
389         opts = Object.merge(opts || {}, {});
390 
391         if (!opts.restart)
392             opts.restart = false;
393 
394         // RunId can come from a number of places
395         // If restart is passed as an old connection
396         if (opts.restart && opts.restart.getDB) {
397             opts.runId = opts.restart.runId;
398             opts.restart = true;
399         }
400         // If it's the runId itself
401         else if (isObject(opts.restart)) {
402             opts.runId = opts.restart;
403             opts.restart = true;
404         }
405 
406         if (isObject(opts.remember)) {
407             opts.runId = opts.remember;
408             opts.remember = true;
409         } else if (opts.remember == undefined) {
410             // Remember by default if we're restarting
411             opts.remember = opts.restart;
412         }
413 
414         // If we passed in restart : <conn> or runId : <conn>
415         if (isObject(opts.runId) && opts.runId.runId)
416             opts.runId = opts.runId.runId;
417 
418         if (opts.restart && opts.remember) {
419             opts = Object.merge(MongoRunner.savedOptions[opts.runId], opts);
420         }
421 
422         // Create a new runId
423         opts.runId = opts.runId || ObjectId();
424 
425         if (opts.forgetPort) {
426             delete opts.port;
427         }
428 
429         // Normalize and get the binary version to use
430         if (opts.hasOwnProperty('binVersion')) {
431             opts.binVersion = MongoRunner.getBinVersionFor(opts.binVersion);
432         }
433 
434         // Default for waitForConnect is true
435         opts.waitForConnect =
436             (waitForConnect == undefined || waitForConnect == null) ? true : waitForConnect;
437 
438         opts.port = opts.port || allocatePort();
439 
440         opts.pathOpts =
441             Object.merge(opts.pathOpts || {}, {port: "" + opts.port, runId: "" + opts.runId});
442 
443         var shouldRemember =
444             (!opts.restart && !opts.noRemember) || (opts.restart && opts.appendOptions);
445         if (shouldRemember) {
446             MongoRunner.savedOptions[opts.runId] = Object.merge(opts, {});
447         }
448 
449         if (jsTestOptions().networkMessageCompressors) {
450             opts.networkMessageCompressors = jsTestOptions().networkMessageCompressors;
451         }
452 
453         return opts;
454     };
455 
456     /**
457      * @option {object} opts
458      *
459      *   {
460      *     dbpath {string}
461      *     useLogFiles {boolean}: use with logFile option.
462      *     logFile {string}: path to the log file. If not specified and useLogFiles
463      *       is true, automatically creates a log file inside dbpath.
464      *     noJournalPrealloc {boolean}
465      *     noJournal {boolean}
466      *     keyFile
467      *     replSet
468      *     oplogSize
469      *   }
470      */
471     MongoRunner.mongodOptions = function(opts) {
472 
473         opts = MongoRunner.mongoOptions(opts);
474 
475         opts.dbpath = MongoRunner.toRealDir(opts.dbpath || "$dataDir/mongod-$port", opts.pathOpts);
476 
477         opts.pathOpts = Object.merge(opts.pathOpts, {dbpath: opts.dbpath});
478 
479         if (!opts.logFile && opts.useLogFiles) {
480             opts.logFile = opts.dbpath + "/mongod.log";
481         } else if (opts.logFile) {
482             opts.logFile = MongoRunner.toRealFile(opts.logFile, opts.pathOpts);
483         }
484 
485         if (opts.logFile !== undefined) {
486             opts.logpath = opts.logFile;
487         }
488 
489         if (jsTestOptions().noJournalPrealloc || opts.noJournalPrealloc)
490             opts.nopreallocj = "";
491 
492         if ((jsTestOptions().noJournal || opts.noJournal) && !('journal' in opts) &&
493             !('configsvr' in opts)) {
494             opts.nojournal = "";
495         }
496 
497         if (jsTestOptions().keyFile && !opts.keyFile) {
498             opts.keyFile = jsTestOptions().keyFile;
499         }
500 
501         if (opts.hasOwnProperty("enableEncryption")) {
502             // opts.enableEncryption, if set, must be an empty string
503             if (opts.enableEncryption !== "") {
504                 throw new Error("The enableEncryption option must be an empty string if it is " +
505                                 "specified");
506             }
507         } else if (jsTestOptions().enableEncryption !== undefined) {
508             if (jsTestOptions().enableEncryption !== "") {
509                 throw new Error("The enableEncryption option must be an empty string if it is " +
510                                 "specified");
511             }
512             opts.enableEncryption = "";
513         }
514 
515         if (opts.hasOwnProperty("encryptionKeyFile")) {
516             // opts.encryptionKeyFile, if set, must be a string
517             if (typeof opts.encryptionKeyFile !== "string") {
518                 throw new Error("The encryptionKeyFile option must be a string if it is specified");
519             }
520         } else if (jsTestOptions().encryptionKeyFile !== undefined) {
521             if (typeof(jsTestOptions().encryptionKeyFile) !== "string") {
522                 throw new Error("The encryptionKeyFile option must be a string if it is specified");
523             }
524             opts.encryptionKeyFile = jsTestOptions().encryptionKeyFile;
525         }
526 
527         if (opts.hasOwnProperty("auditDestination")) {
528             // opts.auditDestination, if set, must be a string
529             if (typeof opts.auditDestination !== "string") {
530                 throw new Error("The auditDestination option must be a string if it is specified");
531             }
532         } else if (jsTestOptions().auditDestination !== undefined) {
533             if (typeof(jsTestOptions().auditDestination) !== "string") {
534                 throw new Error("The auditDestination option must be a string if it is specified");
535             }
536             opts.auditDestination = jsTestOptions().auditDestination;
537         }
538 
539         if (opts.hasOwnProperty("enableMajorityReadConcern")) {
540             // opts.enableMajorityReadConcern, if set, must be an empty string
541             if (opts.enableMajorityReadConcern !== "") {
542                 throw new Error("The enableMajorityReadConcern option must be an empty string if " +
543                                 "it is specified");
544             }
545         } else if (jsTestOptions().enableMajorityReadConcern !== undefined) {
546             if (jsTestOptions().enableMajorityReadConcern !== "") {
547                 throw new Error("The enableMajorityReadConcern option must be an empty string if " +
548                                 "it is specified");
549             }
550             opts.enableMajorityReadConcern = "";
551         }
552 
553         if (opts.noReplSet)
554             opts.replSet = null;
555         if (opts.arbiter)
556             opts.oplogSize = 1;
557 
558         return opts;
559     };
560 
561     MongoRunner.mongosOptions = function(opts) {
562         opts = MongoRunner.mongoOptions(opts);
563 
564         // Normalize configdb option to be host string if currently a host
565         if (opts.configdb && opts.configdb.getDB) {
566             opts.configdb = opts.configdb.host;
567         }
568 
569         opts.pathOpts =
570             Object.merge(opts.pathOpts, {configdb: opts.configdb.replace(/:|\/|,/g, "-")});
571 
572         if (!opts.logFile && opts.useLogFiles) {
573             opts.logFile =
574                 MongoRunner.toRealFile("$dataDir/mongos-$configdb-$port.log", opts.pathOpts);
575         } else if (opts.logFile) {
576             opts.logFile = MongoRunner.toRealFile(opts.logFile, opts.pathOpts);
577         }
578 
579         if (opts.logFile !== undefined) {
580             opts.logpath = opts.logFile;
581         }
582 
583         var testOptions = jsTestOptions();
584         if (testOptions.keyFile && !opts.keyFile) {
585             opts.keyFile = testOptions.keyFile;
586         }
587 
588         if (opts.hasOwnProperty("auditDestination")) {
589             // opts.auditDestination, if set, must be a string
590             if (typeof opts.auditDestination !== "string") {
591                 throw new Error("The auditDestination option must be a string if it is specified");
592             }
593         } else if (testOptions.auditDestination !== undefined) {
594             if (typeof(testOptions.auditDestination) !== "string") {
595                 throw new Error("The auditDestination option must be a string if it is specified");
596             }
597             opts.auditDestination = testOptions.auditDestination;
598         }
599 
600         if (!opts.hasOwnProperty('binVersion') && testOptions.mongosBinVersion) {
601             opts.binVersion = MongoRunner.getBinVersionFor(testOptions.mongosBinVersion);
602         }
603 
604         return opts;
605     };
606 
607     /**
608      * Starts a mongod instance.
609      *
610      * @param {Object} opts
611      *
612      *   {
613      *     useHostName {boolean}: Uses hostname of machine if true.
614      *     forceLock {boolean}: Deletes the lock file if set to true.
615      *     dbpath {string}: location of db files.
616      *     cleanData {boolean}: Removes all files in dbpath if true.
617      *     startClean {boolean}: same as cleanData.
618      *     noCleanData {boolean}: Do not clean files (cleanData takes priority).
619      *     binVersion {string}: version for binary (also see MongoRunner.binVersionSubs).
620      *
621      *     @see MongoRunner.mongodOptions for other options
622      *   }
623      *
624      * @return {Mongo} connection object to the started mongod instance.
625      *
626      * @see MongoRunner.arrOptions
627      */
628     MongoRunner.runMongod = function(opts) {
629 
630         opts = opts || {};
631         var env = undefined;
632         var useHostName = true;
633         var runId = null;
634         var waitForConnect = true;
635         var fullOptions = opts;
636 
637         if (isObject(opts)) {
638             opts = MongoRunner.mongodOptions(opts);
639             fullOptions = opts;
640 
641             if (opts.useHostName != undefined) {
642                 useHostName = opts.useHostName;
643             } else if (opts.useHostname != undefined) {
644                 useHostName = opts.useHostname;
645             } else {
646                 useHostName = true;  // Default to true
647             }
648             env = opts.env;
649             runId = opts.runId;
650             waitForConnect = opts.waitForConnect;
651 
652             if (opts.forceLock)
653                 removeFile(opts.dbpath + "/mongod.lock");
654             if ((opts.cleanData || opts.startClean) || (!opts.restart && !opts.noCleanData)) {
655                 print("Resetting db path '" + opts.dbpath + "'");
656                 resetDbpath(opts.dbpath);
657             }
658 
659             opts = MongoRunner.arrOptions("mongod", opts);
660         }
661 
662         var mongod = MongoRunner._startWithArgs(opts, env, waitForConnect);
663         if (!mongod) {
664             return null;
665         }
666 
667         mongod.commandLine = MongoRunner.arrToOpts(opts);
668         mongod.name = (useHostName ? getHostName() : "localhost") + ":" + mongod.commandLine.port;
669         mongod.host = mongod.name;
670         mongod.port = parseInt(mongod.commandLine.port);
671         mongod.runId = runId || ObjectId();
672         mongod.dbpath = fullOptions.dbpath;
673         mongod.savedOptions = MongoRunner.savedOptions[mongod.runId];
674         mongod.fullOptions = fullOptions;
675 
676         return mongod;
677     };
678 
679     MongoRunner.runMongos = function(opts) {
680         opts = opts || {};
681 
682         var env = undefined;
683         var useHostName = false;
684         var runId = null;
685         var waitForConnect = true;
686         var fullOptions = opts;
687 
688         if (isObject(opts)) {
689             opts = MongoRunner.mongosOptions(opts);
690             fullOptions = opts;
691 
692             useHostName = opts.useHostName || opts.useHostname;
693             runId = opts.runId;
694             waitForConnect = opts.waitForConnect;
695             env = opts.env;
696 
697             opts = MongoRunner.arrOptions("mongos", opts);
698         }
699 
700         var mongos = MongoRunner._startWithArgs(opts, env, waitForConnect);
701         if (!mongos) {
702             return null;
703         }
704 
705         mongos.commandLine = MongoRunner.arrToOpts(opts);
706         mongos.name = (useHostName ? getHostName() : "localhost") + ":" + mongos.commandLine.port;
707         mongos.host = mongos.name;
708         mongos.port = parseInt(mongos.commandLine.port);
709         mongos.runId = runId || ObjectId();
710         mongos.savedOptions = MongoRunner.savedOptions[mongos.runId];
711         mongos.fullOptions = fullOptions;
712 
713         return mongos;
714     };
715 
716     MongoRunner.StopError = function(message, returnCode) {
717         this.name = "StopError";
718         this.returnCode = returnCode || "non-zero";
719         this.message = message || "MongoDB process stopped with exit code: " + this.returnCode;
720         this.stack = this.toString() + "\n" + (new Error()).stack;
721     };
722 
723     MongoRunner.StopError.prototype = Object.create(Error.prototype);
724     MongoRunner.StopError.prototype.constructor = MongoRunner.StopError;
725 
726     // Constants for exit codes of MongoDB processes
727     MongoRunner.EXIT_ABORT = -6;
728     MongoRunner.EXIT_CLEAN = 0;
729     MongoRunner.EXIT_BADOPTIONS = 2;
730     MongoRunner.EXIT_REPLICATION_ERROR = 3;
731     MongoRunner.EXIT_NEED_UPGRADE = 4;
732     MongoRunner.EXIT_SHARDING_ERROR = 5;
733     MongoRunner.EXIT_KILL = 12;
734     MongoRunner.EXIT_ABRUPT = 14;
735     MongoRunner.EXIT_NTSERVICE_ERROR = 20;
736     MongoRunner.EXIT_JAVA = 21;
737     MongoRunner.EXIT_OOM_MALLOC = 42;
738     MongoRunner.EXIT_OOM_REALLOC = 43;
739     MongoRunner.EXIT_FS = 45;
740     MongoRunner.EXIT_CLOCK_SKEW = 47;  // OpTime clock skew; deprecated
741     MongoRunner.EXIT_NET_ERROR = 48;
742     MongoRunner.EXIT_WINDOWS_SERVICE_STOP = 49;
743     MongoRunner.EXIT_POSSIBLE_CORRUPTION = 60;
744     MongoRunner.EXIT_UNCAUGHT = 100;  // top level exception that wasn't caught
745     MongoRunner.EXIT_TEST = 101;
746 
747     /**
748      * Kills a mongod process.
749      *
750      * @param {number} port the port of the process to kill
751      * @param {number} signal The signal number to use for killing
752      * @param {Object} opts Additional options. Format:
753      *    {
754      *      auth: {
755      *        user {string}: admin user name
756      *        pwd {string}: admin password
757      *      }
758      *    }
759      *
760      * Note: The auth option is required in a authenticated mongod running in Windows since
761      *  it uses the shutdown command, which requires admin credentials.
762      */
763     MongoRunner.stopMongod = function(port, signal, opts) {
764 
765         if (!port) {
766             print("Cannot stop mongo process " + port);
767             return null;
768         }
769 
770         signal = parseInt(signal) || 15;
771         opts = opts || {};
772 
773         var allowedExitCodes = [MongoRunner.EXIT_CLEAN];
774 
775         if (_isWindows()) {
776             // Return code of processes killed with TerminateProcess on Windows
777             allowedExitCodes.push(1);
778         } else {
779             // Return code of processes killed with SIGKILL on POSIX systems
780             allowedExitCodes.push(-9);
781         }
782 
783         if (opts.allowedExitCodes) {
784             allowedExitCodes = allowedExitCodes.concat(opts.allowedExitCodes);
785         }
786 
787         if (port.port)
788             port = parseInt(port.port);
789 
790         if (port instanceof ObjectId) {
791             var opts = MongoRunner.savedOptions(port);
792             if (opts)
793                 port = parseInt(opts.port);
794         }
795 
796         var returnCode = _stopMongoProgram(parseInt(port), signal, opts);
797 
798         if (!Array.contains(allowedExitCodes, returnCode)) {
799             throw new MongoRunner.StopError(
800                 `MongoDB process on port ${port} exited with error code ${returnCode}`, returnCode);
801         }
802 
803         return returnCode;
804     };
805 
806     MongoRunner.stopMongos = MongoRunner.stopMongod;
807 
808     /**
809      * Starts an instance of the specified mongo tool
810      *
811      * @param {String} binaryName - The name of the tool to run.
812      * @param {Object} [opts={}] - Options of the form --flag or --key=value to pass to the tool.
813      * @param {string} [opts.binVersion] - The version of the tool to run.
814      *
815      * @param {...string} positionalArgs - Positional arguments to pass to the tool after all
816      * options have been specified. For example,
817      * MongoRunner.runMongoTool("executable", {key: value}, arg1, arg2) would invoke
818      * ./executable --key value arg1 arg2.
819      *
820      * @see MongoRunner.arrOptions
821      */
822     MongoRunner.runMongoTool = function(binaryName, opts, ...positionalArgs) {
823 
824         var opts = opts || {};
825         // Normalize and get the binary version to use
826         opts.binVersion = MongoRunner.getBinVersionFor(opts.binVersion);
827 
828         // Recent versions of the mongo tools support a --dialTimeout flag to set for how
829         // long they retry connecting to a mongod or mongos process. We have them retry
830         // connecting for up to 30 seconds to handle when the tests are run on a
831         // resource-constrained host machine.
832         //
833         // The bsondump tool doesn't accept the --dialTimeout flag because it doesn't connect to a
834         // mongod or mongos process.
835         if (!opts.hasOwnProperty('dialTimeout') && binaryName !== 'bsondump' &&
836             _toolVersionSupportsDialTimeout(opts.binVersion)) {
837             opts['dialTimeout'] = '30';
838         }
839 
840         // Convert 'opts' into an array of arguments.
841         var argsArray = MongoRunner.arrOptions(binaryName, opts);
842 
843         // Append any positional arguments that were specified.
844         argsArray.push(...positionalArgs);
845 
846         return runMongoProgram.apply(null, argsArray);
847 
848     };
849 
850     var _toolVersionSupportsDialTimeout = function(version) {
851         if (version === "latest" || version === "") {
852             return true;
853         }
854         var versionParts =
855             convertVersionStringToArray(version).slice(0, 3).map(part => parseInt(part, 10));
856         if (versionParts.length === 2) {
857             versionParts.push(Infinity);
858         }
859 
860         if (versionParts[0] > 3 || (versionParts[0] === 3 && versionParts[1] > 3)) {
861             // The --dialTimeout command line option is supported by the tools
862             // with a major version newer than 3.3.
863             return true;
864         }
865 
866         for (var supportedVersion of["3.3.4", "3.2.5", "3.0.12"]) {
867             var supportedVersionParts = convertVersionStringToArray(supportedVersion)
868                                             .slice(0, 3)
869                                             .map(part => parseInt(part, 10));
870             if (versionParts[0] === supportedVersionParts[0] &&
871                 versionParts[1] === supportedVersionParts[1] &&
872                 versionParts[2] >= supportedVersionParts[2]) {
873                 return true;
874             }
875         }
876         return false;
877     };
878 
879     // Given a test name figures out a directory for that test to use for dump files and makes sure
880     // that directory exists and is empty.
881     MongoRunner.getAndPrepareDumpDirectory = function(testName) {
882         var dir = MongoRunner.dataPath + testName + "_external/";
883         resetDbpath(dir);
884         return dir;
885     };
886 
887     // Start a mongod instance and return a 'Mongo' object connected to it.
888     // This function's arguments are passed as command line arguments to mongod.
889     // The specified 'dbpath' is cleared if it exists, created if not.
890     // var conn = _startMongodEmpty("--port", 30000, "--dbpath", "asdf");
891     var _startMongodEmpty = function() {
892         var args = createMongoArgs("mongod", Array.from(arguments));
893 
894         var dbpath = _parsePath.apply(null, args);
895         resetDbpath(dbpath);
896 
897         return startMongoProgram.apply(null, args);
898     };
899 
900     _startMongod = function() {
901         print("startMongod WARNING DELETES DATA DIRECTORY THIS IS FOR TESTING ONLY");
902         return _startMongodEmpty.apply(null, arguments);
903     };
904 
905     /**
906      * Returns a new argArray with any test-specific arguments added.
907      */
908     function appendSetParameterArgs(argArray) {
909         var programName = argArray[0];
910         if (programName.endsWith('mongod') || programName.endsWith('mongos') ||
911             programName.startsWith('mongod-') || programName.startsWith('mongos-')) {
912             if (jsTest.options().enableTestCommands) {
913                 argArray.push(...['--setParameter', "enableTestCommands=1"]);
914             }
915             if (jsTest.options().authMechanism && jsTest.options().authMechanism != "SCRAM-SHA-1") {
916                 var hasAuthMechs = false;
917                 for (var i in argArray) {
918                     if (typeof argArray[i] === 'string' &&
919                         argArray[i].indexOf('authenticationMechanisms') != -1) {
920                         hasAuthMechs = true;
921                         break;
922                     }
923                 }
924                 if (!hasAuthMechs) {
925                     argArray.push(
926                         ...['--setParameter',
927                             "authenticationMechanisms=" + jsTest.options().authMechanism]);
928                 }
929             }
930             if (jsTest.options().auth) {
931                 argArray.push(...['--setParameter', "enableLocalhostAuthBypass=false"]);
932             }
933 
934             // mongos only options. Note: excludes mongos with version suffix (ie. mongos-3.0).
935             if (programName.endsWith('mongos')) {
936                 // apply setParameters for mongos
937                 if (jsTest.options().setParametersMongos) {
938                     var params = jsTest.options().setParametersMongos.split(",");
939                     if (params && params.length > 0) {
940                         params.forEach(function(p) {
941                             if (p)
942                                 argArray.push(...['--setParameter', p]);
943                         });
944                     }
945                 }
946             }
947             // mongod only options. Note: excludes mongos with version suffix (ie. mongos-3.0).
948             else if (programName.endsWith('mongod')) {
949                 // set storageEngine for mongod
950                 if (jsTest.options().storageEngine) {
951                     if (argArray.indexOf("--storageEngine") < 0) {
952                         argArray.push(...['--storageEngine', jsTest.options().storageEngine]);
953                     }
954                 }
955                 if (jsTest.options().storageEngineCacheSizeGB) {
956                     if (jsTest.options().storageEngine === "rocksdb") {
957                         argArray.push(
958                             ...['--rocksdbCacheSizeGB', jsTest.options().storageEngineCacheSizeGB]);
959                     } else if (jsTest.options().storageEngine === "wiredTiger" ||
960                                !jsTest.options().storageEngine) {
961                         argArray.push(...['--wiredTigerCacheSizeGB',
962                                           jsTest.options().storageEngineCacheSizeGB]);
963                     }
964                 }
965                 if (jsTest.options().wiredTigerEngineConfigString) {
966                     argArray.push(...['--wiredTigerEngineConfigString',
967                                       jsTest.options().wiredTigerEngineConfigString]);
968                 }
969                 if (jsTest.options().wiredTigerCollectionConfigString) {
970                     argArray.push(...['--wiredTigerCollectionConfigString',
971                                       jsTest.options().wiredTigerCollectionConfigString]);
972                 }
973                 if (jsTest.options().wiredTigerIndexConfigString) {
974                     argArray.push(...['--wiredTigerIndexConfigString',
975                                       jsTest.options().wiredTigerIndexConfigString]);
976                 }
977                 // apply setParameters for mongod
978                 if (jsTest.options().setParameters) {
979                     var params = jsTest.options().setParameters.split(",");
980                     if (params && params.length > 0) {
981                         params.forEach(function(p) {
982                             if (p)
983                                 argArray.push(...['--setParameter', p]);
984                         });
985                     }
986                 }
987             }
988         }
989         return argArray;
990     }
991 
992     /**
993      * Start a mongo process with a particular argument array.
994      * If we aren't waiting for connect, return {pid: <pid>}.
995      * If we are not waiting for connect:
996      *     returns connection to process on success;
997      *     otherwise returns null if we fail to connect.
998      */
999     MongoRunner._startWithArgs = function(argArray, env, waitForConnect) {
1000         // TODO: Make there only be one codepath for starting mongo processes
1001 
1002         argArray = appendSetParameterArgs(argArray);
1003         var port = _parsePort.apply(null, argArray);
1004         var pid = -1;
1005         if (env === undefined) {
1006             pid = _startMongoProgram.apply(null, argArray);
1007         } else {
1008             pid = _startMongoProgram({args: argArray, env: env});
1009         }
1010 
1011         if (!waitForConnect) {
1012             return {
1013                 pid: pid,
1014             };
1015         }
1016 
1017         var conn = null;
1018         assert.soon(function() {
1019             try {
1020                 conn = new Mongo("127.0.0.1:" + port);
1021                 conn.pid = pid;
1022                 return true;
1023             } catch (e) {
1024                 if (!checkProgram(pid)) {
1025                     print("Could not start mongo program at " + port + ", process ended");
1026 
1027                     // Break out
1028                     return true;
1029                 }
1030             }
1031             return false;
1032         }, "unable to connect to mongo program on port " + port, 600 * 1000);
1033 
1034         return conn;
1035     };
1036 
1037     /**
1038      * DEPRECATED
1039      *
1040      * Start mongod or mongos and return a Mongo() object connected to there.
1041      * This function's first argument is "mongod" or "mongos" program name, \
1042      * and subsequent arguments to this function are passed as
1043      * command line arguments to the program.
1044      */
1045     startMongoProgram = function() {
1046         var port = _parsePort.apply(null, arguments);
1047 
1048         // Enable test commands.
1049         // TODO: Make this work better with multi-version testing so that we can support
1050         // enabling this on 2.4 when testing 2.6
1051         var args = Array.from(arguments);
1052         args = appendSetParameterArgs(args);
1053         var pid = _startMongoProgram.apply(null, args);
1054 
1055         var m;
1056         assert.soon(function() {
1057             try {
1058                 m = new Mongo("127.0.0.1:" + port);
1059                 return true;
1060             } catch (e) {
1061                 if (!checkProgram(pid)) {
1062                     print("Could not start mongo program at " + port + ", process ended");
1063 
1064                     // Break out
1065                     m = null;
1066                     return true;
1067                 }
1068             }
1069             return false;
1070         }, "unable to connect to mongo program on port " + port, 600 * 1000);
1071 
1072         return m;
1073     };
1074 
1075     runMongoProgram = function() {
1076         var args = Array.from(arguments);
1077         args = appendSetParameterArgs(args);
1078         var progName = args[0];
1079 
1080         if (jsTestOptions().auth) {
1081             args = args.slice(1);
1082             args.unshift(progName,
1083                          '-u',
1084                          jsTestOptions().authUser,
1085                          '-p',
1086                          jsTestOptions().authPassword,
1087                          '--authenticationDatabase=admin');
1088         }
1089 
1090         if (progName == 'mongo' && !_useWriteCommandsDefault()) {
1091             progName = args[0];
1092             args = args.slice(1);
1093             args.unshift(progName, '--useLegacyWriteOps');
1094         }
1095 
1096         return _runMongoProgram.apply(null, args);
1097     };
1098 
1099     // Start a mongo program instance.  This function's first argument is the
1100     // program name, and subsequent arguments to this function are passed as
1101     // command line arguments to the program.  Returns pid of the spawned program.
1102     startMongoProgramNoConnect = function() {
1103         var args = Array.from(arguments);
1104         args = appendSetParameterArgs(args);
1105         var progName = args[0];
1106 
1107         if (jsTestOptions().auth) {
1108             args = args.slice(1);
1109             args.unshift(progName,
1110                          '-u',
1111                          jsTestOptions().authUser,
1112                          '-p',
1113                          jsTestOptions().authPassword,
1114                          '--authenticationDatabase=admin');
1115         }
1116 
1117         if (progName == 'mongo' && !_useWriteCommandsDefault()) {
1118             args = args.slice(1);
1119             args.unshift(progName, '--useLegacyWriteOps');
1120         }
1121 
1122         return _startMongoProgram.apply(null, args);
1123     };
1124 
1125     myPort = function() {
1126         var m = db.getMongo();
1127         if (m.host.match(/:/))
1128             return m.host.match(/:(.*)/)[1];
1129         else
1130             return 27017;
1131     };
1132 
1133 }());
1134