diff --git a/celery.js b/celery.js index 95fd635..de12519 100644 --- a/celery.js +++ b/celery.js @@ -1,7 +1,7 @@ var url = require('url'), util = require('util'), amqp = require('amqp'), - redis = require('redis'), + Redis = require('ioredis'), events = require('events'), uuid = require('uuid'); @@ -9,7 +9,7 @@ var createMessage = require('./protocol').createMessage; var debug = process.env.NODE_CELERY_DEBUG === '1' ? console.info : function() {}; -var supportedProtocols = ['amqp', 'amqps', 'redis']; +var supportedProtocols = ['amqp', 'amqps', 'redis', 'sentinel']; function getProtocol(kind, options) { const protocol = url.parse(options.url).protocol.slice(0, -1); if (protocol === 'amqps') { @@ -68,7 +68,7 @@ function Configuration(options) { function RedisBroker(conf) { var self = this; - self.redis = redis.createClient(conf.BROKER_OPTIONS); + self.redis = new Redis(conf.BROKER_OPTIONS); self.end = function() { self.redis.end(true); @@ -118,8 +118,7 @@ util.inherits(RedisBroker, events.EventEmitter); function RedisBackend(conf) { var self = this; - self.redis = redis.createClient(conf.RESULT_BACKEND_OPTIONS); - + self.redis = new Redis(conf.RESULT_BACKEND_OPTIONS); var backend_ex = self.redis.duplicate(); self.redis.on('error', function(err) { @@ -179,7 +178,7 @@ function Client(conf) { self.conf = new Configuration(conf); // backend - if (self.conf.backend_type === 'redis') { + if (self.conf.backend_type === 'redis' || self.conf.backend_type === 'sentinel') { self.backend = new RedisBackend(self.conf); self.backend.on('message', function(msg) { self.emit('message', msg); @@ -198,7 +197,7 @@ function Client(conf) { self.backend.on('ready', function() { debug('Connecting to broker...'); - if (self.conf.broker_type === 'redis') { + if (self.conf.broker_type === 'redis' || self.conf.broker_type === 'sentinel') { self.broker = new RedisBroker(self.conf); } else if (self.conf.broker_type === 'amqp') { self.broker = amqp.createConnection(self.conf.BROKER_OPTIONS, { @@ -252,7 +251,6 @@ Client.prototype.call = function(name /*[args], [kwargs], [options], [callback]* var task = this.createTask(name), result = task.call(args, kwargs, options); - if (callback && result) { debug('Subscribing to result...'); result.on('ready', callback); @@ -275,7 +273,7 @@ function Task(client, name, options, exchange) { var result = new Result(id, self.client); - if (client.conf.backend_type === 'redis') { + if (client.conf.backend_type === 'redis' || client.conf.backend_type === 'sentinel') { client.backend.results[result.taskid] = result; } diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 02a4f95..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: "2" - -services: - redis: - image: redis - ports: - - "6379:6379" - - rabbit: - image: rabbitmq - ports: - - "5672:5672" - - celery_amqp: - image: celery - command: celery worker -A tasks --loglevel=INFO - volumes: - - "./tests/tasks.py:/home/user/tasks.py" - depends_on: - - rabbit - - celery_redis: - image: celery - command: celery worker -A tasks --loglevel=INFO - volumes: - - "./tests/tasks.py:/home/user/tasks.py" - environment: - - CELERY_BROKER_URL=redis://redis/0 - - CELERY_BACKEND_URL=redis://redis/0 - depends_on: - - redis diff --git a/dockers/.env b/dockers/.env new file mode 100644 index 0000000..e9b3068 --- /dev/null +++ b/dockers/.env @@ -0,0 +1 @@ +EXTERNAL_HOST=10.10.2.51 diff --git a/dockers/celery4.2/Dockerfile b/dockers/celery4.2/Dockerfile new file mode 100644 index 0000000..6d50b88 --- /dev/null +++ b/dockers/celery4.2/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.5-slim + +RUN groupadd user && useradd --create-home --home-dir /home/user -g user user +WORKDIR /home/user + +RUN pip install redis + +ENV CELERY_VERSION 4.1 + +RUN pip install celery=="$CELERY_VERSION" + +RUN { \ + echo 'import os'; \ + echo "broker_url = os.environ.get('BROKER_URL', 'sentinel://sentinel:26379/')"; \ + echo "broker_transport_options = { 'master_name': os.environ.get('MASTER_NAME', 'sentinelTest') }"; \ + echo "backend_url = os.environ.get('BACKEND_URL', 'redis://redis/0')"; \ +} > celeryconfig.py + +# --link some-rabbit:rabbit "just works" +# ENV BROKER_URL amqp://guest@rabbit + +USER user +CMD ["celery", "worker"] diff --git a/dockers/docker-compose.yml b/dockers/docker-compose.yml new file mode 100644 index 0000000..d957c77 --- /dev/null +++ b/dockers/docker-compose.yml @@ -0,0 +1,66 @@ +version: "3" + +services: + redis: + image: redis + ports: + - "6379:6379" + + rabbit: + image: rabbitmq + ports: + - "5672:5672" + + celery_amqp: + image: celery + command: celery worker -A tasks --loglevel=INFO + volumes: + - "./tasks.py:/home/user/tasks.py" + depends_on: + - rabbit + + celery_redis: + image: celery + command: celery worker -A tasks --loglevel=INFO + volumes: + - "./tasks.py:/home/user/tasks.py" + environment: + - CELERY_BROKER_URL=redis://redis/0 + - CELERY_BACKEND_URL=redis://redis/0 + depends_on: + - redis + + redis_master: + image: 'redis' + command: redis-server --port 6380 + ports: + - '6380:6380' + + redis_slave: + image: 'redis' + command: redis-server --port 6381 --slaveof "${EXTERNAL_HOST}" 6380 --slave-announce-ip "${EXTERNAL_HOST}" + ports: + - '6381:6381' + + sentinel: + build: ./sentinel + ports: + - '26379:26379' + environment: + - SENTINEL_NAME=sentinelTest + - HOST_IP="${EXTERNAL_HOST}" + depends_on: + - redis_master + - redis_slave + + celery_sentinel: + build: ./celery4.2 + command: celery worker -A tasks --loglevel=INFO + volumes: + - "./sentinel_tasks.py:/home/user/tasks.py" + environment: + - BROKER_URL=sentinel://sentinel:26379 + - MASTER_NAME=sentinelTest + - BACKEND_URL=redis://redis_master:6380/0 + depends_on: + - redis diff --git a/dockers/down.sh b/dockers/down.sh new file mode 100755 index 0000000..f231c01 --- /dev/null +++ b/dockers/down.sh @@ -0,0 +1,7 @@ +#!/bin/bash +set -ev + +pushd `dirname $0` +docker-compose down +docker-compose ps +popd diff --git a/dockers/sentinel/Dockerfile b/dockers/sentinel/Dockerfile new file mode 100644 index 0000000..6a9c1e3 --- /dev/null +++ b/dockers/sentinel/Dockerfile @@ -0,0 +1,11 @@ +FROM redis + +EXPOSE 26379 +COPY sentinel.conf /etc/redis/sentinel.conf +RUN chown redis:redis /etc/redis/sentinel.conf +ENV SENTINEL_QUORUM 2 +ENV SENTINEL_NAME sentinelTest +ENV SENTINEL_DOWN_AFTER 30000 +ENV SENTINEL_FAILOVER 180000 +COPY sentinel-entrypoint.sh /usr/local/bin/ +ENTRYPOINT ["sentinel-entrypoint.sh"] diff --git a/dockers/sentinel/sentinel-entrypoint.sh b/dockers/sentinel/sentinel-entrypoint.sh new file mode 100755 index 0000000..3691728 --- /dev/null +++ b/dockers/sentinel/sentinel-entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +sed -i "s/\$SENTINEL_QUORUM/$SENTINEL_QUORUM/g" /etc/redis/sentinel.conf +sed -i "s/\$SENTINEL_DOWN_AFTER/$SENTINEL_DOWN_AFTER/g" /etc/redis/sentinel.conf +sed -i "s/\$SENTINEL_FAILOVER/$SENTINEL_FAILOVER/g" /etc/redis/sentinel.conf +sed -i "s/\$SENTINEL_NAME/$SENTINEL_NAME/g" /etc/redis/sentinel.conf +sed -i "s/\$HOST_IP/$HOST_IP/g" /etc/redis/sentinel.conf + +exec docker-entrypoint.sh redis-server /etc/redis/sentinel.conf --sentinel diff --git a/dockers/sentinel/sentinel.conf b/dockers/sentinel/sentinel.conf new file mode 100644 index 0000000..b0f2cbc --- /dev/null +++ b/dockers/sentinel/sentinel.conf @@ -0,0 +1,11 @@ +port 26379 + +dir /tmp + +sentinel monitor $SENTINEL_NAME $HOST_IP 6380 $SENTINEL_QUORUM + +sentinel down-after-milliseconds $SENTINEL_NAME $SENTINEL_DOWN_AFTER + +sentinel parallel-syncs $SENTINEL_NAME 1 + +sentinel failover-timeout $SENTINEL_NAME $SENTINEL_FAILOVER diff --git a/dockers/sentinel_tasks.py b/dockers/sentinel_tasks.py new file mode 100644 index 0000000..274ee84 --- /dev/null +++ b/dockers/sentinel_tasks.py @@ -0,0 +1,48 @@ +import os +import logging +from celery import Celery + +celery = Celery('tasks') + +celery.conf.broker_url = os.environ.get('BROKER_URL') +celery.conf.broker_transport_options = { + 'master_name': os.environ.get('MASTER_NAME') +} +celery.conf.result_backend = os.environ.get('BACKEND_URL') + +celery.conf.update( + result_serializer='json', + enable_utc=True +) + + +@celery.task +def add(x, y): + return x + y + + +@celery.task +def sleep(x): + time.sleep(x) + return x + + +@celery.task +def time(): + import time + return time.time() + + +@celery.task +def error(msg): + raise Exception(msg) + + +@celery.task +def echo(msg): + return msg + + +@celery.task +def send_email(to='me@example.com', title='hi'): + logging.info("Sending email to '%s' with title '%s'" % (to, title)) diff --git a/tests/tasks.py b/dockers/tasks.py similarity index 100% rename from tests/tasks.py rename to dockers/tasks.py diff --git a/dockers/up.sh b/dockers/up.sh new file mode 100755 index 0000000..1c29745 --- /dev/null +++ b/dockers/up.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -ev + +pushd `dirname $0` + +# Get external IP of localhost +IP=`ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'` +echo $IP + +echo "EXTERNAL_HOST=$IP" > .env + +# Start the services. +docker-compose up -d --build +docker-compose ps + +popd diff --git a/package.json b/package.json index 9b24255..963e26a 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,12 @@ "description": "Celery client for Node", "author": "Mher Movsisyan ", "scripts": { - "test": "mocha tests" + "test": "mocha tests --exit" }, "dependencies": { "amqp": "*", - "redis": "*", + "ioredis": "^3.2.2", + "redis": "^2.8.0", "uuid": "^3.0.0" }, "repository": { diff --git a/tests/runTests.sh b/tests/runTests.sh deleted file mode 100755 index 7afe2f1..0000000 --- a/tests/runTests.sh +++ /dev/null @@ -1,5 +0,0 @@ -echo 'Running protocol tests...' -mocha test_protocol - -echo 'Running functional tests...' -mocha test_celery diff --git a/tests/test_celery.js b/tests/test_celery.test.js similarity index 79% rename from tests/test_celery.js rename to tests/test_celery.test.js index 9ab908e..c1c10b8 100644 --- a/tests/test_celery.js +++ b/tests/test_celery.test.js @@ -18,6 +18,20 @@ var conf_redis = { TASK_RESULT_EXPIRES: 1 // seconds }; +var conf_sentinel = { + CELERY_BROKER_URL: 'sentinel://', + CELERY_RESULT_BACKEND: 'sentinel://', + TASK_RESULT_EXPIRES: 1, // seconds, + CELERY_BROKER_OPTIONS: { + sentinels: [{ host: '127.0.0.1', port: 26379 }], + name: 'sentinelTest' + }, + RESULT_BACKEND_OPTIONS: { + sentinels: [{ host: '127.0.0.1', port: 26379 }], + name: 'sentinelTest' + } +}; + describe('celery functional tests', function() { describe('initialization', function() { it('should create a valid amqp client without error', function(done) { @@ -75,6 +89,10 @@ describe('celery functional tests', function() { client.on('error', function(exception) { assert.ok(exception); + client.emit('end'); + }); + + client.once('end', function() { done(); }); }); @@ -173,6 +191,45 @@ describe('celery functional tests', function() { }); }); + describe('result handling with redis sentinel backend', function() { + it('should return a task result (poll)', function(done) { + var client = celery.createClient(conf_sentinel), + add = client.createTask('tasks.add'); + + client.on('connect', function() { + var result = add.call([1, 2]); + + setTimeout(function() { + result.get(function(message) { + assert.equal(message.result, 3); + client.end(); + }); + }, 500); + }); + + client.on('end', function() { + done(); + }); + }); + + it('should return a task result (push)', function(done) { + var client = celery.createClient(conf_sentinel), + add = client.createTask('tasks.add'); + + client.on('connect', function() { + var result = add.call([1, 2]); + result.on('ready', function(message) { + assert.equal(message.result, 3); + client.end(); + }); + }); + + client.on('end', function() { + done(); + }); + }); + }); + describe('eta', function() { it('should call a task with a delay', function(done) { var client = celery.createClient(conf_amqp), diff --git a/tests/test_protocol.js b/tests/test_protocol.test.js similarity index 100% rename from tests/test_protocol.js rename to tests/test_protocol.test.js diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..6644ecb --- /dev/null +++ b/yarn.lock @@ -0,0 +1,269 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +amqp@*: + version "0.2.6" + resolved "https://registry.yarnpkg.com/amqp/-/amqp-0.2.6.tgz#d97fee5143026fa0b4fd6a5d56485f0448eb37ca" + dependencies: + lodash "^4.0.0" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +bluebird@^3.3.4: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + +cluster-key-slot@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.0.8.tgz#7654556085a65330932a2e8b5976f8e2d0b3e414" + +commander@2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +denque@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.2.3.tgz#98c50c8dd8cdfae318cc5859cc8ee3da0f9b0cc2" + +diff@3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + +escape-string-regexp@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +flexbuffer@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/flexbuffer/-/flexbuffer-0.0.6.tgz#039fdf23f8823e440c38f3277e6fef1174215b30" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +glob@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +growl@1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +he@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ioredis@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-3.2.2.tgz#b7d5ff3afd77bb9718bb2821329b894b9a44c00b" + dependencies: + bluebird "^3.3.4" + cluster-key-slot "^1.0.6" + debug "^2.6.9" + denque "^1.1.0" + flexbuffer "0.0.6" + lodash.assign "^4.2.0" + lodash.bind "^4.2.1" + lodash.clone "^4.5.0" + lodash.clonedeep "^4.5.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.foreach "^4.5.0" + lodash.isempty "^4.4.0" + lodash.keys "^4.2.0" + lodash.noop "^3.0.1" + lodash.partial "^4.2.1" + lodash.pick "^4.4.0" + lodash.sample "^4.2.1" + lodash.shuffle "^4.2.0" + lodash.values "^4.3.0" + redis-commands "^1.2.0" + redis-parser "^2.4.0" + +lodash.assign@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" + +lodash.bind@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.bind/-/lodash.bind-4.2.1.tgz#7ae3017e939622ac31b7d7d7dcb1b34db1690d35" + +lodash.clone@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + +lodash.foreach@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.foreach/-/lodash.foreach-4.5.0.tgz#1a6a35eace401280c7f06dddec35165ab27e3e53" + +lodash.isempty@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" + +lodash.keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-4.2.0.tgz#a08602ac12e4fb83f91fc1fb7a360a4d9ba35205" + +lodash.noop@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c" + +lodash.partial@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.partial/-/lodash.partial-4.2.1.tgz#49f3d8cfdaa3bff8b3a91d127e923245418961d4" + +lodash.pick@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" + +lodash.sample@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/lodash.sample/-/lodash.sample-4.2.1.tgz#5e4291b0c753fa1abeb0aab8fb29df1b66f07f6d" + +lodash.shuffle@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.shuffle/-/lodash.shuffle-4.2.0.tgz#145b5053cf875f6f5c2a33f48b6e9948c6ec7b4b" + +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + +lodash@^4.0.0: + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +mkdirp@0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +mocha@*: + version "5.0.5" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.0.5.tgz#e228e3386b9387a4710007a641f127b00be44b52" + dependencies: + browser-stdout "1.3.1" + commander "2.11.0" + debug "3.1.0" + diff "3.5.0" + escape-string-regexp "1.0.5" + glob "7.1.2" + growl "1.10.3" + he "1.1.1" + mkdirp "0.5.1" + supports-color "4.4.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +redis-commands@^1.2.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.5.tgz#4495889414f1e886261180b1442e7295602d83a2" + +redis-parser@^2.4.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" + +supports-color@4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" + dependencies: + has-flag "^2.0.0" + +uuid@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"