<|start_filename|>failpoint.js<|end_filename|>
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';
/* eslint max-len: 0, max-statements: 0 */

module.exports = Failpoint;

var EventEmitter = require('events').EventEmitter;
var robb = require('robb/src/robb');
var util = require('util');

function Failpoint(options) {
    options = options || {};

    if (typeof options.name !== 'string') {
        throw new Error('Requires `name` as string');
    }

    this.name = options.name;
    this.failpoints = options.failpoints;
    this.Math = options.Math || Math;
    this.Date = options.Date || Date;
    this.probability = 0.0;
    this.maxCount = null;
    this.maxDurationMs = null;
    this.args = null;
    this.setTime = null;
    this.triggerCount = null;
    this.lastTriggered = null;
    this.hitMaxLimits = false;
}

util.inherits(Failpoint, EventEmitter);

Failpoint.prototype.toJSON = function toJSON() {
    var self = this;
    return {
        name: self.name,
        probability: self.probability,
        maxCount: self.maxCount,
        maxDurationMs: self.maxDurationMs,
        args: self.args,
        setTime: self.setTime,
        triggerCount: self.triggerCount,
        lastTriggered: self.lastTriggered
    };
};

Failpoint.prototype.setState = function setState(options) {
    /* eslint complexity: 0 */
    var self = this;
    if (typeof options === 'boolean') {
        if (options) {
            self.probability = 1.0;
        } else {
            self.probability = 0.0;
        }
        self.maxCount = null;
        self.maxDurationMs = null;
        self.args = null;
        return self._resetStatsVars();
    }

    if (!options ||
        typeof options.probability !== 'number' ||
        !(options.probability >= 0.0 && options.probability <= 1.0)) {
        throw new Error('`options.probability` not between 0.0 to 1.0');
    }

    if (options.maxCount !== null &&
        options.maxCount !== undefined &&
        (!robb.isInt(options.maxCount) || options.maxCount <= 0)) {
        throw new Error('`options.maxCount` not an integer greater than 0');
    }

    if (options.maxDurationMs !== null &&
        options.maxDurationMs !== undefined &&
        (typeof options.maxDurationMs !== 'number' || options.maxDurationMs <= 0)) {
        throw new Error('`options.maxDurationMs` not a duration greater than 0ms');
    }

    if (options.args !== null &&
        options.args !== undefined &&
        (typeof options.args !== 'object' ||
        !Array.isArray(Object.keys(options.args)))) {
        throw new Error('`options.args` not an object with at least 1 key');
    }

    self.probability = options.probability;
    self.maxCount = options.maxCount || null;
    self.maxDurationMs = options.maxDurationMs || null;
    self.args = options.args || null;
    self._resetStatsVars();
};

Failpoint.prototype._resetStatsVars = function resetStatsVars() {
    var self = this;
    self.hitMaxLimits = false;
    if (self.probability > 0.0) {
        self.emit('active', self);
        self.setTime = self.Date.now();
        self.triggerCount = 0;
        self.lastTriggered = null;
    } else {
        self.emit('inactive', self);
        self.setTime = null;
        self.triggerCount = null;
        self.lastTriggered = null;
    }
};

Failpoint.prototype.shouldFail = function shouldFail() {
    var self = this;

    if (self.hitMaxLimits) {
        // Fast path for avoiding counts & times
        return false;
    }

    if (self.maxCount !== null && self.triggerCount >= self.maxCount) {
        // Cache hitting the max count limit
        self.hitMaxLimits = true;
        self.emit('inactive', self);
        return false;
    }

    if (self.maxDurationMs !== null && (self.Date.now() - self.setTime) > self.maxDurationMs) {
        // Cache hitting the max duration limit
        self.hitMaxLimits = true;
        self.emit('inactive', self);
        return false;
    }

    var didPassProbabilityTest = false;
    if (self.probability === 1.0 || self.Math.random() <= self.probability) {
        didPassProbabilityTest = true;
    }

    if (!didPassProbabilityTest) {
        return false;
    }

    self.triggerCount++;
    self.lastTriggered = self.Date.now();

    return true;
};


<|start_filename|>test/failpoint.js<|end_filename|>
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';
/* eslint max-len: 0 */

var Failpoint = require('../failpoint');
var test = require('tape');

test('Failpoint throws when name not set correctly', function t(assert) {
    assert.throws(function tryPassBadName() {
        Failpoint();
    }, /name/);
    assert.end();
});

test('Failpoint.setState throws on bad probability', function t(assert) {
    assert.throws(function tryPassBadProbability() {
        var failpoint = new Failpoint({name: 'test'});
        failpoint.setState({probability: 2.0});
    });
    assert.end();
});

test('Failpoint.setState throws on bad maxCount', function t(assert) {
    assert.throws(function tryPassBadProbability() {
        var failpoint = new Failpoint({name: 'test'});
        failpoint.setState({probability: 1.0, maxCount: -1});
    });
    assert.end();
});

test('Failpoint.setState throws on bad maxDurationMs', function t(assert) {
    assert.throws(function tryPassBadProbability() {
        var failpoint = new Failpoint({name: 'test'});
        failpoint.setState({probability: 1.0, maxDurationMs: -1});
    });
    assert.end();
});

test('Failpoint.setState throws on bad args', function t(assert) {
    assert.throws(function tryPassBadProbability() {
        var failpoint = new Failpoint({name: 'test'});
        failpoint.setState({probability: 1.0, args: 'args'});
    });
    assert.end();
});

test('Failpoint.shouldFail returns true if random() <= probability', function t(assert) {
    function MockMath() { }
    MockMath.random = function mockMathRandom() {
        return 0.2;
    };

    var failpoint = new Failpoint({name: 'test', Math: MockMath});
    failpoint.setState({probability: 0.5});
    assert.equal(failpoint.shouldFail(), true);
    assert.end();
});

test('Failpoint.shouldFail returns false if random() > probability', function t(assert) {
    function MockMath() { }
    MockMath.random = function mockMathRandom() {
        return 0.8;
    };

    var failpoint = new Failpoint({name: 'test', Math: MockMath});
    failpoint.setState({probability: 0.5});
    assert.equal(failpoint.shouldFail(), false);
    assert.end();
});

test('Failpoint.shouldFail returns false if exceeds maxCount', function t(assert) {
    var failpoint = new Failpoint({name: 'test'});
    failpoint.setState({probability: 1.0, maxCount: 1});
    assert.equal(failpoint.shouldFail(), true);
    assert.equal(failpoint.shouldFail(), false);
    assert.end();
});

test('Failpoint.shouldFail returns false if exceeds maxDurationMs', function t(assert) {
    var i = 0;
    var start = Date.now();
    var failpoint = new Failpoint({
        name: 'test',
        Date: {
            now: function mockNow() {
                var value = i++ > 1 ? start + 101 : start;
                return value;
            }
        }
    });
    failpoint.setState({probability: 1.0, maxDurationMs: 100});
    assert.equal(failpoint.shouldFail(), true);
    assert.equal(failpoint.shouldFail(), false);
    assert.end();
});

test('Failpoint.shouldFail returns false through hitMaxLimits fast path if exceeded maxCount', function t(assert) {
    var failpoint = new Failpoint({name: 'test'});
    failpoint.setState({probability: 1.0, maxCount: 1});
    assert.equal(failpoint.shouldFail(), true);
    assert.equal(failpoint.shouldFail(), false);
    assert.equal(failpoint.hitMaxLimits, true);
    assert.equal(failpoint.shouldFail(), false);
    assert.end();
});


<|start_filename|>package.json<|end_filename|>
{
  "name": "failpoints",
  "version": "2.2.0",
  "description": "A failpoints javascript library",
  "keywords": [],
  "repository": "git://github.com/uber/failpointsjs.git",
  "main": "index",
  "contributors": [
    "<NAME> <<EMAIL>>"
  ],
  "dependencies": {
    "es6-map": "^0.1.1",
    "individual": "^3.0.0",
    "robb": "^0.2.1",
    "uuid": "^2.0.1"
  },
  "devDependencies": {
    "istanbul": "^0.3.14",
    "opn": "^1.0.2",
    "tap-spec": "^3.0.0",
    "tape": "^4.0.0",
    "uber-licence": "^1.5.1",
    "uber-standard": "^3.6.4"
  },
  "scripts": {
    "test": "npm run lint && node test/index.js | tap-spec",
    "just-test": "node test/index.js | tap-spec",
    "add-licence": "uber-licence",
    "check-licence": "uber-licence --dry",
    "cover": "istanbul cover --print detail --report html test/index.js && istanbul check-coverage --branches=100",
    "just-cover": "istanbul cover --print detail --report html test/index.js",
    "lint": "uber-standard",
    "view-cover": "opn coverage/index.html"
  },
  "pre-commit": [
    "check-licence",
    "cover"
  ]
}


<|start_filename|>index.js<|end_filename|>
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';
/* eslint max-len: 0, max-statements: 0 */

module.exports = Failpoints;

/* eslint-disable */
var Map = require('es6-map');
/* eslint-enable */

var EventEmitter = require('events').EventEmitter;
var Failpoint = require('./failpoint');
var Individual = require('individual');
var util = require('util');
var UUID = require('uuid');

var CONST = {};
CONST.DEFAULT_NAMESPACE = 'DEFAULT_NAMESPACE';
CONST.NO_ARGS = Object.freeze({});
CONST = Object.freeze(CONST);

function Failpoints(options) {
    options = options || {};

    if (typeof options.namespace !== 'string') {
        throw new Error('Requires `namespace` as string');
    }

    this.namespace = options.namespace;
    this.activeFailpoints = new Map();
    this.Math = options.Math || Math;
    this.Date = options.Date || Date;
}

util.inherits(Failpoints, EventEmitter);

Failpoints.CONST = CONST;

Failpoints.failpointsByNamespace = Individual('__FAILPOINTS_BY_NAMESPACE', new Map());

Failpoints.getOrCreateFailpointsWithNamespace = function getOrCreateFailpointsWithNamespace(namespace) {
    var byNamespace = Failpoints.failpointsByNamespace;
    var failpoints = byNamespace.get(namespace);
    if (!failpoints) {
        failpoints = new Failpoints({namespace: namespace});
        byNamespace.set(namespace, failpoints);
    }
    return failpoints;
};

Failpoints.getFailpointsWithDefaultNamespace = function getFailpointsWithDefaultNamespace() {
    return Failpoints.getOrCreateFailpointsWithNamespace(CONST.DEFAULT_NAMESPACE);
};

Failpoints.createWithNamespace = function createWithNamespace(namespace) {
    return this.getOrCreateFailpointsWithNamespace(namespace);
};

Failpoints.create = function createUntracked() {
    return new Failpoints({namespace: 'Failpoints-' + UUID.v4()});
};

/**
 * Set a failpoint's state. Can either be a registered failpoint or not-yet-registered failpoint.
 *
 * Will throw on bad options.
 *
 * @param {String} name - Name of the failpoint to enable/disable
 * @param {Boolean|Object} options - If boolean then turn it completely on or off, otherwise set as options object
 */
Failpoints.prototype.set = function set(name, options) {
    /*
     * options:
     *  probability {Number} Float between 0-1 with probability on each call to be on or off
     *  maxCount {Number} Integer of many times at most to invoke as on before reducing to probability 0
     *  maxDurationMs {Number} Integer of milliseconds to run at most before reducing probability to 0
     *  args {Object} Arguments to pass to failpoint selection method if provided at failpoint
     */
    var self = this;

    var failpoint = self.activeFailpoints.get(name);
    if (!failpoint) {
        failpoint = new Failpoint({
            name: name,
            failpoints: self,
            Math: self.Math,
            Date: self.Date
        });
        failpoint.once('inactive', function onInactive() {
            // This failpoint has reached limit or set inactive, removing to increase performance
            var sizeBeforeDeletion = self.activeFailpoints.size;
            self.activeFailpoints.delete(name);
            var sizeAfterDeletion = self.activeFailpoints.size;
            self.emit('activeCount', sizeAfterDeletion);

            if (sizeAfterDeletion === 0 && sizeBeforeDeletion > 0) {
                self.emit('becameInactive');
            }
        });
        var sizeBeforeInsertion = self.activeFailpoints.size;
        self.activeFailpoints.set(name, failpoint);
        self.emit('activeCount', self.activeFailpoints.size);
        if (sizeBeforeInsertion === 0) {
            self.emit('becameActive');
        }
    }

    failpoint.setState(options);
};

/**
 * Set all known failpoints' state.
 *
 * Will throw on bad options.
 *
 * @param {Boolean|Object} options - If boolean then turn it completely on or off, otherwise set as options object
 */
Failpoints.prototype.setAll = function setAll(options) {
    var self = this;
    self.activeFailpoints.forEach(function eachFailpoint(failpoint, name) {
        failpoint.setState(options);
    });
};

Failpoints.prototype.get = function get(name) {
    var self = this;
    var failpoint = self.activeFailpoints.get(name);
    if (failpoint) {
        return failpoint.toJSON();
    }
    return undefined;
};

Failpoints.prototype.getArgs = function getArgs(name) {
    var self = this;
    var failpoint = self.activeFailpoints.get(name);
    if (!failpoint) {
        return undefined;
    }
    return failpoint.args || CONST.NO_ARGS;
};

Failpoints.prototype.shouldFail = function shouldFail(name) {
    var self = this;
    var failpoint = self.activeFailpoints.get(name);
    if (!failpoint) {
        return false;
    }
    return failpoint.shouldFail();
};

Failpoints.prototype.shouldFailConditionally = function shouldFailConditionally(name, shouldAllow) {
    var self = this;
    if (typeof shouldAllow !== 'function') {
        throw new Error('shouldFailConditionally does not have shouldAllow callback set');
    }
    var failpoint = self.activeFailpoints.get(name);
    if (!failpoint) {
        return false;
    }
    var args = failpoint.args || CONST.NO_ARGS;
    // Explicitly check hitMaxLimits first to avoid
    // potentially expensive shouldAllow function call
    return !failpoint.hitMaxLimits && shouldAllow(args) && failpoint.shouldFail();
};

Failpoints.prototype.inline = function inline(name, onShouldFail, onNormally) {
    var self = this;
    if (typeof onShouldFail !== 'function') {
        throw new Error('inline does not have onShouldFail callback set');
    }
    if (self.shouldFail(name)) {
        onShouldFail(self.getArgs(name));
        return true;
    } else if (typeof onNormally === 'function') {
        onNormally();
        return false;
    } else {
        return false;
    }
};

Failpoints.prototype.inlineConditionally = function inlineConditionally(name, shouldAllow, onShouldFail, onNormally) {
    var self = this;
    if (typeof onShouldFail !== 'function') {
        throw new Error('inlineConditionally does not have onShouldFail callback set');
    }
    if (self.shouldFailConditionally(name, shouldAllow)) {
        onShouldFail(self.getArgs(name));
        return true;
    } else if (typeof onNormally === 'function') {
        onNormally();
        return false;
    } else {
        return false;
    }
};

Failpoints.prototype.inlineSync = function inlineSync(name, onShouldFail) {
    var self = this;
    if (typeof onShouldFail !== 'function') {
        throw new Error('inlineSync does not have onShouldFail callback set');
    }
    if (self.shouldFail(name)) {
        onShouldFail(self.getArgs(name));
        return true;
    } else {
        return false;
    }
};

Failpoints.prototype.inlineSyncConditionally = function inlineSyncConditionally(name, shouldAllow, onShouldFail) {
    var self = this;
    if (typeof onShouldFail !== 'function') {
        throw new Error('inlineSyncConditionally does not have onShouldFail callback set');
    }
    if (self.shouldFailConditionally(name, shouldAllow)) {
        onShouldFail(self.getArgs(name));
        return true;
    } else {
        return false;
    }
};


<|start_filename|>test/inline.js<|end_filename|>
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';
/* eslint max-len: 0 */

var createFailpoints = require('../').create;
var mockDateTypeWithFixedDateNow = require('./test_utils').mockDateTypeWithFixedDateNow;
var test = require('tape');

test('inline failpoint can fail', function t(assert) {
    var myFailpointCallCount = 0;

    var failpoints = createFailpoints();
    failpoints.Date = mockDateTypeWithFixedDateNow();

    methodUnderTest();

    failpoints.set('my_failpoint', true);

    methodUnderTest();
    methodUnderTest();

    failpoints.set('my_failpoint', false);

    methodUnderTest();

    assert.equal(myFailpointCallCount, 2);

    assert.end();

    function methodUnderTest() {
        failpoints.inline('my_failpoint', function onShouldFail() {
            myFailpointCallCount++;
        });
    }
});

test('inlineConditionally failpoint can fail', function t(assert) {
    var myFailpointCallCount = 0;
    var avoidedMyFailpointCallCount = 0;

    var failpoints = createFailpoints();
    failpoints.Date = mockDateTypeWithFixedDateNow();

    failpoints.set('my_maybe_failpoint', {probability: 1, args: {userId: 456}});

    methodUnderTestWithUserId(123);
    methodUnderTestWithUserId(456);

    assert.equal(myFailpointCallCount, 1);
    assert.equal(avoidedMyFailpointCallCount, 1);

    assert.end();

    function methodUnderTestWithUserId(userId) {
        failpoints.inlineConditionally('my_maybe_failpoint', function shouldAllow(args) {
            return args.userId === userId;
        }, function onShouldFail() {
            myFailpointCallCount++;
        }, function onNormally() {
            avoidedMyFailpointCallCount++;
        });
    }
});


<|start_filename|>test/failpoints.js<|end_filename|>
// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';
/* eslint max-len: 0, max-statements: 0 */

var Failpoints = require('../');
var mockDateTypeWithFixedDateNow = require('./test_utils').mockDateTypeWithFixedDateNow;
var test = require('tape');
var UUID = require('uuid');

test('Failpoints throws when name not set correctly', function t(assert) {
    assert.throws(function tryPassBadName() {
        Failpoints();
    }, /name/);
    assert.end();
});

test('Failpoints.getOrCreateFailpointsWithNamespace can lazy create namespace', function t(assert) {
    var uuid = UUID.v4();
    assert.ok(Failpoints.getOrCreateFailpointsWithNamespace('lazy' + uuid));
    assert.end();
});

test('Failpoints.getOrCreateFailpointsWithNamespace can get created namespace', function t(assert) {
    var uuid = UUID.v4();
    var created = Failpoints.getOrCreateFailpointsWithNamespace('created' + uuid);
    assert.ok(created);
    var retrieved = Failpoints.getOrCreateFailpointsWithNamespace('created' + uuid);
    assert.equal(retrieved, created);
    assert.end();
});

test('Failpoints emits activeCount when adding an active failpoint and when it becomes inactive', function t(assert) {
    var activeCountCalls = [];
    var becameActiveCallCount = 0;
    var becameInactiveCallCount = 0;
    var failpoints = new Failpoints({namespace: 'test'});
    failpoints.on('activeCount', onActiveCount);
    failpoints.on('becameActive', onBecameActive);
    failpoints.on('becameInactive', onBecameInactive);

    failpoints.set('my_failpoint', {probability: 1.0, maxCount: 1});
    assert.equal(becameActiveCallCount, 1);

    failpoints.set('my_second_failpoint', {probability: 1.0, maxCount: 1});

    failpoints.shouldFail('my_failpoint');
    failpoints.shouldFail('my_failpoint');
    failpoints.shouldFail('my_second_failpoint');
    failpoints.shouldFail('my_second_failpoint');
    assert.equal(becameInactiveCallCount, 1);

    assert.deepEqual(activeCountCalls, [1, 2, 1, 0]);

    assert.end();

    function onActiveCount(count) {
        activeCountCalls.push(count);
    }

    function onBecameActive() {
        becameActiveCallCount++;
    }

    function onBecameInactive() {
        becameInactiveCallCount++;
    }
});

test('Failpoints.get can get failpoint as JSON', function t(assert) {
    var mockDate = mockDateTypeWithFixedDateNow();
    var failpoints = new Failpoints({
        namespace: 'test',
        Date: mockDate
    });
    failpoints.set('my_failpoint', {
        probability: 0.5,
        maxDurationMs: 100
    });
    assert.deepEqual(failpoints.get('my_failpoint'), {
        name: 'my_failpoint',
        probability: 0.5,
        maxCount: null,
        maxDurationMs: 100,
        args: null,
        setTime: mockDate.now(),
        triggerCount: 0,
        lastTriggered: null
    });
    assert.end();
});

test('Failpoints.get returns undefined for unknown failpoint', function t(assert) {
    var failpoints = new Failpoints({namespace: 'test'});
    assert.equal(failpoints.get('missing_failpoint'), undefined);
    assert.end();
});

test('Failpoints.getArgs returns args for failpoint', function t(assert) {
    var failpoints = new Failpoints({namespace: 'test'});
    var args = {my: 'args'};
    failpoints.set('my_failpoint', {
        probability: 1.0,
        maxDurationMs: 100,
        args: args
    });
    assert.deepEqual(failpoints.getArgs('my_failpoint'), {my: 'args'});
    assert.end();
});

test('Failpoints.getArgs returns undefined for unknown failpoint', function t(assert) {
    var failpoints = new Failpoints({namespace: 'test'});
    assert.equal(failpoints.getArgs('missing_failpoint'), undefined);
    assert.end();
});

test('Failpoints.shouldFailConditionally returns empty args object if no args to shouldAllow', function t(assert) {
    var failpoints = new Failpoints({namespace: 'test'});
    failpoints.set('my_failpoint', {probability: 1.0});
    var result = failpoints.shouldFailConditionally('my_failpoint', function shouldAllow(args) {
        assert.deepEqual(args, {});
        return true;
    });
    assert.equal(result, true);
    assert.end();
});

test('Failpoints.shouldFailConditionally throws on shouldAllow not set', function t(assert) {
    assert.throws(function tryPassBadShouldAllow() {
        var failpoints = new Failpoints({namespace: 'test'});
        failpoints.shouldFailConditionally('my_failpoint');
    });
    assert.end();
});

test('Failpoints.shouldFailConditionally returns false on unknown failpoint', function t(assert) {
    var failpoints = new Failpoints({namespace: 'test'});
    assert.equal(failpoints.shouldFailConditionally('my_failpoint', shouldAllow), false);
    assert.end();

    function shouldAllow() {
        return true;
    }
});

test('Failpoints.inline calls onNormally when set', function t(assert) {
    var onNormallyCallCount = 0;
    var failpoints = new Failpoints({namespace: 'test'});
    failpoints.inline('my_failpoint', function shouldAllow() {
        return true;
    }, function onNormally() {
        onNormallyCallCount++;
    });
    assert.equal(onNormallyCallCount, 1);
    assert.end();
});

test('Failpoints.inline throws on onShouldFail not set', function t(assert) {
    assert.throws(function tryPassBadonShouldFail() {
        var failpoints = new Failpoints({namespace: 'test'});
        failpoints.inline('my_failpoint');
    });
    assert.end();
});

test('Failpoints.inlineConditionally returns false when should fail without onNormallyCallCount', function t(assert) {
    var onShouldFailCallCount = 0;
    var failpoints = new Failpoints({namespace: 'test'});
    var result = failpoints.inlineConditionally('my_failpoint', function shouldAllow() {
        return true;
    }, function onShouldFail() {
        onShouldFailCallCount++;
    });
    assert.equal(result, false);
    assert.equal(onShouldFailCallCount, 0);
    assert.end();
});

test('Failpoints.inlineConditionally calls onShouldFail when set', function t(assert) {
    var onShouldFailCallCount = 0;
    var failpoints = new Failpoints({namespace: 'test'});
    failpoints.set('my_failpoint', {probability: 1.0});
    failpoints.inlineConditionally('my_failpoint', function shouldAllow() {
        return true;
    }, function onShouldFail() {
        onShouldFailCallCount++;
    });
    assert.equal(onShouldFailCallCount, 1);
    assert.end();
});

test('Failpoints.inlineConditionally throws on onShouldFail not set', function t(assert) {
    assert.throws(function tryPassBadonShouldFail() {
        var failpoints = new Failpoints({namespace: 'test'});
        failpoints.inlineConditionally('my_failpoint');
    });
    assert.end();
});

test('Failpoints.inlineSync throws on onShouldFail not set', function t(assert) {
    assert.throws(function tryPassBadonShouldFail() {
        var failpoints = new Failpoints({namespace: 'test'});
        failpoints.inlineSync('my_failpoint');
    });
    assert.end();
});

test('Failpoints.inlineSyncConditionally calls onShouldFail when should fail', function t(assert) {
    var onShouldFailCallCount = 0;
    var failpoints = new Failpoints({namespace: 'test'});
    failpoints.set('my_failpoint', {probability: 1.0});
    failpoints.inlineSyncConditionally('my_failpoint', function shouldAllow() {
        return true;
    }, function onShouldFail() {
        onShouldFailCallCount++;
    });
    assert.equal(onShouldFailCallCount, 1);
    assert.end();
});

test('Failpoints.inlineSyncConditionally does not call onShouldFail when should not fail', function t(assert) {
    var onShouldFailCallCount = 0;
    var failpoints = new Failpoints({namespace: 'test'});
    failpoints.inlineSyncConditionally('my_failpoint', function shouldAllow() {
        return true;
    }, function onShouldFail() {
        onShouldFailCallCount++;
    });
    assert.equal(onShouldFailCallCount, 0);
    assert.end();
});

test('Failpoints.inlineSyncConditionally throws on onShouldFail not set', function t(assert) {
    assert.throws(function tryPassBadonShouldFail() {
        var failpoints = new Failpoints({namespace: 'test'});
        failpoints.inlineSyncConditionally('my_failpoint');
    });
    assert.end();
});

test('Failpoints.inlineSyncConditionally throws on onShouldFail not set', function t(assert) {
    assert.throws(function tryPassBadonShouldFail() {
        var failpoints = new Failpoints({namespace: 'test'});
        failpoints.inlineSyncConditionally('my_failpoint');
    });
    assert.end();
});
