...
 
Commits (164)
# ensure that we only run ci test on the Node.js enabled servers
# Ensure that we only run CI test on the Node.js enabled servers.
language: node_js
node_js:
- 0.8
- "0.8"
- "0.10"
# create a travis enabled environment for the test suite to run in
script: "make travisci"
# Create a Travis memcached enabled environment for the test suite to run in and
# ensure that we test against localhost on Travis-CI.
services: memcache
env: MEMCACHED__HOST=localhost
# the `sevices: memcache` will start a memcached service on localhost
# and on the default port, but in order to test against multiple memcache
# The `sevices: memcache` will start a memcached service on localhost
# and on the default port, but in order to test against multiple memcached
# instances we need to spawn a couple more, so we do that during the before
# script
before_script:
- memcached -p 11212 -d
- memcached -p 11213 -d
# Fix broken builds caused by packages using the carrot semver bullshit.
before_install:
- "npm install -g npm@2.1.x"
# Authors and contributors of the node Memcached driver project in alphabetical order.
- Alfonso Boza
- Anton Onyshchenko
- Arek Flinik
- Arnout Kazemier
- Jan Krems
- Jason Pearlman
- Joseph Mordetsky
- Near Privman
- Nebojsa Sabovic
- Ron Korving
- Sebastian Seilund
- Tobias Müllerleile
0.2.1
- Supports for a queued callback limit so it would crash the process when we queue
to much callbacks. #81
0.2.0
- [breaking] We are now returning Error instances instead of strings for errors
- Dependency bump for a critical bug in our connection pool.
0.1.5
- Don't execute callbacks multiple times if the connection fails
- Parser fix for handling server responses that contain Memcached Procotol
keywords
- Make sure that the retry option is set correctly
0.1.4
- Added missing error listener to the 3rd-Eden/jackpot module, this prevents crashes
when it's unable to connect to a server.
0.1.3
- Handle Memcached responses that contain no value.
- Travis CI integration.
0.1.2
- Returning an error when the Memcached server issues a NOT_STORED response.
0.1.1
- Now using 3rd-Eden/jackpot as connection pool, this should give a more stable
connection.
0.1.0
- Storing numeric values are now returned as numeric values, they are no
longer strings.
0.0.12
- Added .setEncoding to the connections, this way UTF-8 chars will not be
chopped in to multiple pieces and breaking binary stored data or UTF-8 text
0.0.11
- Added more useful error messages instead of returning false. Please note
that they are still strings instead of Error instances (legacy)
0.0.10
- Compatibility with Node.js 0.8
- Don't saturate the Node process by retrying to connect if pool is full #43
- Minor code formatting
0.0.9
- Codestyle refactor, named the functions, removed tabs
- Added Mocha test suite
ALL_TESTS = $(shell find test -name '*.test.js')
test:
@./node_modules/.bin/mocha $(ALL_TESTS)
travisci:
MEMCACHED__HOST=localhost $(MAKE) test
doc:
dox --title "node-memcached" lib/* > doc/index.html
.PHONY: test doc
This diff is collapsed.
This diff is collapsed.
......@@ -12,9 +12,9 @@ var Ring = new HashRing(
);
// Return the server based on the key
process.stdout.write( Ring.getNode( "my-super-secret-cache-key" ) );
process.stdout.write( Ring.getNode( "hello-world" ) );
process.stdout.write( Ring.getNode( "my-super-secret-cache-key" ) );
process.stdout.write( Ring.get( "my-super-secret-cache-key" ) );
process.stdout.write( Ring.get( "hello-world" ) );
process.stdout.write( Ring.get( "my-super-secret-cache-key" ) );
// Different algorithms produce different hash maps. So choose wisely
var sha1Ring = new HashRing(
......@@ -30,6 +30,6 @@ var sha1Ring = new HashRing(
'sha1' // optional algorithm for key hashing
);
process.stdout.write( sha1Ring.getNode( "my-super-secret-cache-key" ) );
process.stdout.write( sha1Ring.getNode( "hello-world" ) );
process.stdout.write( sha1Ring.getNode( "my-super-secret-cache-key" ) );
process.stdout.write( sha1Ring.get( "my-super-secret-cache-key" ) );
process.stdout.write( sha1Ring.get( "hello-world" ) );
process.stdout.write( sha1Ring.get( "my-super-secret-cache-key" ) );
......@@ -2,7 +2,8 @@
var EventEmitter = require('events').EventEmitter
, spawn = require('child_process').spawn
, Utils = require('./utils');
, Utils = require('./utils')
, util = require('util');
exports.IssueLog = IssueLog; // connection issue handling
exports.Available = ping; // connection availablity
......@@ -25,8 +26,10 @@ function IssueLog (args) {
this.config = args;
this.messages = [];
this.failed = false;
this.locked = false;
this.isScheduledToReconnect = false;
this.totalRetries = 0;
this.totalFailures = 0;
this.retry = 0;
this.totalReconnectsAttempted = 0;
this.totalReconnectsSuccess = 0;
......@@ -35,7 +38,8 @@ function IssueLog (args) {
EventEmitter.call(this);
}
var issues = IssueLog.prototype = new EventEmitter;
util.inherits(IssueLog, EventEmitter);
var issues = IssueLog.prototype;
issues.log = function log (message) {
var issue = this;
......@@ -43,32 +47,48 @@ issues.log = function log (message) {
this.failed = true;
this.messages.push(message || 'No message specified');
if (this.retries) {
// All failures must occur within `failuresTimeout` ms from the initial
// failure in order for node to be disconnected or removed.
if (this.failures && this.failures == this.config.failures)
this.failuresResetId = setTimeout(issue.failuresReset.bind(issue), this.failuresTimeout);
if (this.failures && !this.locked) {
this.locked = true;
setTimeout(issue.attemptRetry.bind(issue), this.retry);
return this.emit('issue', this.details);
}
if (this.failuresResetId) clearTimeout(this.failuresResetId);
if (this.remove) return this.emit('remove', this.details);
setTimeout(issue.attemptReconnect.bind(issue), this.reconnect);
if (!this.isScheduledToReconnect) {
this.isScheduledToReconnect = true;
setTimeout(issue.attemptReconnect.bind(issue), this.reconnect);
}
};
issues.failuresReset = function failuresReset() {
//this.failures = this.config.failures;
Utils.merge(this, JSON.parse(JSON.stringify(this.config)));
};
Object.defineProperty(issues, 'details', {
get: function getDetails () {
var res = {};
res.server = this.serverAddress;
res.server = this.server;
res.tokens = this.tokens;
res.messages = this.messages;
if (this.retries) {
res.retries = this.retries;
res.totalRetries = this.totalRetries;
if (this.failures) {
res.failures = this.failures;
res.totalFailures = this.totalFailures;
} else {
res.totalReconnectsAttempted = this.totalReconnectsAttempted;
res.totalReconnectsSuccess = this.totalReconnectsSuccess;
res.totalReconnectsFailed = this.totalReconnectsAttempted - this.totalReconnectsSuccess;
res.totalDownTime = (res.totalReconnectsFailed * this.reconnect) + (this.totalRetries * this.retry);
res.totalDownTime = (res.totalReconnectsFailed * this.reconnect) + (this.totalFailures * this.retry);
}
return res;
......@@ -76,9 +96,10 @@ Object.defineProperty(issues, 'details', {
});
issues.attemptRetry = function attemptRetry () {
this.totalRetries++;
this.retries--;
this.totalFailures++;
this.failures--;
this.failed = false;
this.locked = false;
};
issues.attemptReconnect = function attemptReconnect () {
......@@ -90,7 +111,7 @@ issues.attemptReconnect = function attemptReconnect () {
ping(this.tokens[1], function pingpong (err) {
// still no access to the server
if (err) {
this.messages.push(err.message || 'No message specified');
issue.messages.push(err.message || 'No message specified');
return setTimeout(issue.attemptReconnect.bind(issue), issue.reconnect);
}
......@@ -99,6 +120,7 @@ issues.attemptReconnect = function attemptReconnect () {
issue.totalReconnectsSuccess++;
issue.messages.length = 0;
issue.failed = false;
issue.isScheduledToReconnect = false;
// we connected again, so we are going through the whole cycle again
Utils.merge(issue, JSON.parse(JSON.stringify(issue.config)));
......
This diff is collapsed.
......@@ -29,7 +29,17 @@ exports.validateArg = function validateArg (args, config) {
if (toString.call(value) !== '[object Array]') {
err = 'Argument "' + key + '" is not a valid Array.';
}
if (!err && key === 'key') {
for (var vKey in value) {
var vValue = value[vKey];
var result = validateKeySize(config, vKey, vValue);
if (result.err) {
err = result.err;
} else {
args.command = args.command.replace(vValue, result['value']);
}
}
}
break;
case Object:
......@@ -52,17 +62,11 @@ exports.validateArg = function validateArg (args, config) {
}
if (!err && key === 'key') {
if (value.length > config.maxKeySize) {
if (config.keyCompression){
args[key] = createHash('md5').update(value).digest('hex');
// also make sure you update the command
args.command = args.command.replace(value, args[key]);
} else {
err = 'Argument "' + key + '" is longer than the maximum allowed length of ' + config.maxKeySize;
}
} else if (/[\s\n\r]/.test(value)) {
err = 'The key should not contain any whitespace or new lines';
var result = validateKeySize(config, key, value);
if (result.err) {
err = result.err;
} else {
args.command = args.command.replace(value, result['value']);
}
}
break;
......@@ -75,16 +79,30 @@ exports.validateArg = function validateArg (args, config) {
});
if (err){
if(args.callback) args.callback(new Error(err));
if (args.callback) args.callback(new Error(err));
return false;
}
return true;
};
var validateKeySize = function validateKeySize(config, key, value) {
if (value.length > config.maxKeySize) {
if (config.keyCompression){
return { err: false, value: createHash('md5').update(value).digest('hex') };
} else {
return { err: 'Argument "' + key + '" is longer than the maximum allowed length of ' + config.maxKeySize };
}
} else if (/[\s\n\r]/.test(value)) {
return { err: 'The key should not contain any whitespace or new lines' };
} else {
return { err: false, value: value };
}
};
// a small util to use an object for eventEmitter
exports.fuse = function fuse (target, handlers) {
for(var i in handlers)
for (var i in handlers)
if (handlers.hasOwnProperty(i)){
target.on(i, handlers[i]);
}
......@@ -137,4 +155,4 @@ exports.escapeValue = function(value) {
//Unescapes escaped values by removing backslashes before line breaks
exports.unescapeValue = function(value) {
return value.replace(/\\(\r|\n)/g, '$1');
};
\ No newline at end of file
};
{
"name": "memcached"
, "version": "0.2.1"
, "author": "Arnout Kazemier"
, "description": "A fully featured Memcached API client, supporting both single and clustered Memcached servers through consistent hashing and failover/failure. Memcached is rewrite of nMemcached, which will be deprecated in the near future."
, "main": "index"
, "keywords":[
"memcached"
, "client"
, "hashing"
, "failover"
, "cluster"
, "nMemcached"
, "memcache"
, "cache"
, "nosql"
, "membase"
, "InnoDB memcached API"
]
, "directories": {
"lib": "./lib"
}
, "maintainers": [{
"name": "Arnout Kazemier"
, "email": "info@3rd-Eden.com"
, "url": "http://www.3rd-Eden.com"
}]
, "license": {
"type": "MIT"
, "url": "http://github.com/3rd-Eden/node-memcached/blob/master/LICENSE"
}
, "repository": {
"type": "git"
, "url" : "http://github.com/3rd-Eden/node-memcached.git"
}
, "dependencies": {
"hashring": "0.0.x"
, "jackpot": ">=0.0.2"
}
, "devDependencies": {
"mocha": "*"
, "should": "*"
}
, "scripts": {
"test": "./node_modules/.bin/mocha $(shell find test -name '*.test.js')"
}
"name": "memcached",
"version": "2.1.0",
"author": "Arnout Kazemier",
"description": "A fully featured Memcached API client, supporting both single and clustered Memcached servers through consistent hashing and failover/failure. Memcached is rewrite of nMemcached, which will be deprecated in the near future.",
"main": "index",
"keywords": [
"InnoDB memcached API",
"cache",
"client",
"cluster",
"failover",
"hashing",
"membase",
"memcache",
"memcached",
"nMemcached",
"nosql"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "http://github.com/3rd-Eden/node-memcached.git"
},
"dependencies": {
"hashring": "3.1.x",
"jackpot": ">=0.0.6"
},
"devDependencies": {
"mocha": "*",
"should": "*",
"pre-commit": "*"
},
"scripts": {
"test": "mocha $(find test -name '*.test.js')"
}
}
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
......@@ -13,23 +14,23 @@ global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(
* Expresso test suite for all `add` related
* memcached commands
*/
describe("Memcached ADD", function() {
describe('Memcached ADD', function () {
/**
* Make sure that adding a key which already exists returns an error.
*/
it("fail to add an already existing key", function(done) {
it('fail to add an already existing key', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.add("test:" + testnr, message, 1000, function(error, answer){
memcached.add('test:' + testnr, message, 1000, function (error, answer) {
++callbacks;
assert.ok(error);
......@@ -37,9 +38,7 @@ describe("Memcached ADD", function() {
memcached.end(); // close connections
assert.equal(callbacks, 2);
done();
});
});
});
});
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, common = require('./common')
, Memcached = require('../');
......@@ -12,32 +13,32 @@ global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(
* Expresso test suite for all `get` related
* memcached commands
*/
describe("Memcached CAS", function() {
describe('Memcached CAS', function () {
/**
* For a proper CAS update in memcached you will need to know the CAS value
* of a given key, this is done by the `gets` command. So we will need to make
* sure that a `cas` key is given.
*/
it("set and gets for cas result", function(done) {
it('set and gets for cas result', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.gets("test:" + testnr, function(error, answer){
memcached.gets('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(typeof answer === 'object');
assert.ok(!!answer.cas);
answer["test:" + testnr].should.eql(message);
answer['test:' + testnr].should.eql(message);
memcached.end(); // close connections
assert.equal(callbacks, 2);
......@@ -49,30 +50,30 @@ describe("Memcached CAS", function() {
/**
* Create a successful cas update, so we are sure we send a cas request correctly.
*/
it("successful cas update", function(done) {
it('successful cas update', function(done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.gets("test:" + testnr, function(error, answer){
memcached.gets('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!!answer.cas);
// generate new message for the cas update
message = common.alphabet(256);
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){
memcached.cas('test:' + testnr, message, answer.cas, 1000, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!!answer);
memcached.get("test:" + testnr, function(error, answer){
memcached.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
......@@ -81,7 +82,7 @@ describe("Memcached CAS", function() {
memcached.end(); // close connections
assert.equal(callbacks, 4);
done();
})
});
});
});
});
......@@ -91,33 +92,33 @@ describe("Memcached CAS", function() {
* Create a unsuccessful cas update, which would indicate that the server has changed
* while we where doing nothing.
*/
it("unsuccessful cas update", function(done) {
it('unsuccessful cas update', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1000, function(error, ok){
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.gets("test:" + testnr, function(error, answer){
memcached.gets('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!!answer.cas);
// generate new message
message = common.alphabet(256);
memcached.set("test:" + testnr, message, 1000, function(){
memcached.set('test:' + testnr, message, 1000, function () {
++callbacks;
memcached.cas("test:" + testnr, message, answer.cas, 1000, function(error, answer){
memcached.cas('test:' + testnr, message, answer.cas, 1000, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(!answer);
memcached.get("test:" + testnr, function(error, answer){
memcached.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
......
......@@ -33,4 +33,237 @@ describe('Memcached connections', function () {
done();
});
});
it('should remove a failed server', function(done) {
var memcached = new Memcached('127.0.1:1234', {
timeout: 1000,
retries: 0,
failures: 0,
retry: 100,
remove: true });
this.timeout(60000);
memcached.get('idontcare', function (err) {
function noserver() {
memcached.get('idontcare', function(err) {
throw err;
});
};
assert.throws(noserver, new RegExp('Server at 127.0.1.1234 not available'));
memcached.end();
done();
});
});
it('should rebalance to remaining healthy server', function(done) {
var memcached = new Memcached(['127.0.1:1234', common.servers.single], {
timeout: 1000,
retries: 0,
failures: 0,
retry: 100,
remove: true,
redundancy: true });
this.timeout(60000);
// 'a' goes to fake server. first request will cause server to be removed
memcached.get('a', function (err) {
// second request should be rebalanced to healthy server
memcached.get('a', function (err) {
assert.ifError(err);
memcached.end();
done();
});
});
});
it('should properly schedule failed server retries', function(done) {
var server = '127.0.0.1:1234';
var memcached = new Memcached(server, {
retries: 0,
failures: 5,
retry: 100 });
// First request will schedule a retry attempt, and lock scheduling
memcached.get('idontcare', function (err) {
assert.throws(function() { throw err }, /connect ECONNREFUSED/);
assert.deepEqual(memcached.issues[server].failures, 5);
assert.deepEqual(memcached.issues[server].locked, true);
assert.deepEqual(memcached.issues[server].failed, true);
// Immediate request should not decrement failures
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /not available/);
assert.deepEqual(memcached.issues[server].failures, 5);
assert.deepEqual(memcached.issues[server].locked, true);
assert.deepEqual(memcached.issues[server].failed, true);
// Once `retry` time has passed, failures should decrement by one
setTimeout(function() {
// Server should be back in action
assert.deepEqual(memcached.issues[server].locked, false);
assert.deepEqual(memcached.issues[server].failed, false);
memcached.get('idontcare', function(err) {
// Server should be marked healthy again, though we'll get this error
assert.throws(function() { throw err }, /connect ECONNREFUSED/);
assert.deepEqual(memcached.issues[server].failures, 4);
memcached.end();
done();
});
}, 100); // `retry` is 100 so wait 100
});
});
});
it('should properly schedule server reconnection attempts', function(done) {
var server = '127.0.0.1:1234'
, memcached = new Memcached(server, {
retries: 3,
minTimeout: 0,
maxTimeout: 100,
failures: 0,
reconnect: 100 })
, reconnectAttempts = 0;
memcached.on('reconnecting', function() {
reconnectAttempts++;
});
// First request will mark server dead and schedule reconnect
memcached.get('idontcare', function (err) {
assert.throws(function() { throw err }, /connect ECONNREFUSED/);
// Second request should not schedule another reconnect
memcached.get('idontcare', function (err) {
assert.throws(function() { throw err }, /not available/);
// Allow enough time to pass for a connection retries to occur
setTimeout(function() {
assert.deepEqual(reconnectAttempts, 1);
memcached.end();
done();
}, 400);
});
});
});
it('should reset failures after reconnecting to failed server', function(done) {
var server = '127.0.0.1:1234'
, memcached = new Memcached(server, {
retries: 0,
minTimeout: 0,
maxTimeout: 100,
failures: 1,
retry: 1,
reconnect: 100 })
this.timeout(60000);
// First request will mark server failed
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /connect ECONNREFUSED/);
// Wait 10ms, server should be back online
setTimeout(function() {
// Second request will mark server dead
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /connect ECONNREFUSED/);
// Third request should find no servers
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /not available/);
// Give enough time for server to reconnect
setTimeout(function() {
// Server should be reconnected, but expect ECONNREFUSED
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /connect ECONNREFUSED/);
assert.deepEqual(memcached.issues[server].failures,
memcached.issues[server].config.failures);
memcached.end();
done();
});
}, 150);
});
});
},10);
});
});
it('should default to port 11211', function(done) {
// Use an IP without port
var server = '127.0.0.1'
, memcached = new Memcached(server);
memcached.get('idontcare', function(err) {
assert.ifError(err);
assert.equal(Object.keys(memcached.connections)[0], '127.0.0.1:11211');
memcached.end();
done();
});
});
it('should return error on connection timeout', function(done) {
// Use a non routable IP
var server = '10.255.255.255:1234'
, memcached = new Memcached(server, {
retries: 0,
timeout: 100,
idle: 1000,
failures: 0 });
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /Timed out while trying to establish connection/);
memcached.end();
done();
});
});
it('should remove connection when idle', function(done) {
var memcached = new Memcached(common.servers.single, {
retries: 0,
timeout: 100,
idle: 100,
failures: 0 });
memcached.get('idontcare', function(err) {
assert.deepEqual(memcached.connections[common.servers.single].pool.length, 1);
setTimeout(function() {
assert.deepEqual(memcached.connections[common.servers.single].pool.length, 0);
memcached.end();
done();
}, 100);
});
});
it('should remove server if error occurs after connection established', function(done) {
var memcached = new Memcached(common.servers.single, {
poolSize: 1,
retries: 0,
timeout: 1000,
idle: 5000,
failures: 0 });
// Should work fine
memcached.get('idontcare', function(err) {
assert.ifError(err);
// Fake an error on the connected socket which should mark server failed
var S = memcached.connections[common.servers.single].pool.pop();
S.emit('error', new Error('Dummy error'));
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err; }, /not available/);
done();
});
});
});
it('should reset failures if all failures do not occur within failuresTimeout ms', function(done) {
var server = '10.255.255.255:1234'
, memcached = new Memcached(server, {
retries: 0,
timeout: 10,
idle: 1000,
retry: 10,
failures: 2,
failuresTimeout: 100 });
memcached.get('idontcare', function(err) {
assert.throws(function() { throw err }, /Timed out while trying to establish connection/);
// Allow `retry` ms to pass, which will decrement failures
setTimeout(function() {
assert.deepEqual(memcached.issues[server].failures, 1);
// Allow failuresTimeout ms to pass, which should reset failures
setTimeout(function() {
assert.deepEqual(memcached.issues[server].failures,
memcached.issues[server].config.failures);
memcached.end();
done();
}, 100);
}, 15);
});
});
});
This diff is collapsed.
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
, Memcached = require('../');
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed();
/**
* Expresso test suite for all `get` related
* memcached commands
*/
describe('Memcached tests with Namespaces', function () {
/**
* Make sure that the string that we send to the server is correctly
* stored and retrieved. We will be storing random strings to ensure
* that we are not retrieving old data.
*/
it("set with one namespace and verify it can't be read in another", function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
// Load an non-namespaced entry to memcached
memcached.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
var memcachedOther = new Memcached(common.servers.single, {
namespace: 'mySegmentedMemcached:'
});
// Try to load that memcache key with the namespace prepended - this should fail
memcachedOther.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
assert.ok(answer===undefined);
// OK, now let's put it in with the namespace prepended
memcachedOther.set('test:' + testnr, message, 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
// Now when we request it back, it should be there
memcachedOther.get('test:' + testnr, function (error, answer) {
++callbacks;
assert.ok(!error);
assert.ok(typeof answer === 'string');
answer.should.eql(message);
memcachedOther.end(); // close connections
assert.equal(callbacks, 4);
done();
});
});
});
});
});
it('set, set, and multiget with custom namespace', function (done) {
var memcached = new Memcached(common.servers.single, {
namespace: 'mySegmentedMemcached:'
})
, callbacks = 0;
// Load two namespaced variables into memcached
memcached.set('test1', 'test1answer', 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.set('test2', 'test2answer', 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.get(['test1', 'test2'], function (error, answer) {
++callbacks;
assert.ok(typeof answer === 'object');
answer.test1.should.eql('test1answer');
answer.test2.should.eql('test2answer');
memcached.end(); // close connections
assert.equal(callbacks, 3);
done();
});
});
});
});
/**
* In this case, these keys will be allocated to servers like below.
* test1,3,4 => :11211
* test5 => :11212
* test2 => :11213
*/
it('multi get from multi server with custom namespace (inc. cache miss)', function (done) {
var memcached = new Memcached(common.servers.multi, {
namespace: 'mySegmentedMemcached:'
})
, callbacks = 0;
// Load two namespaced variables into memcached
memcached.set('test1', 'test1answer', 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.set('test2', 'test2answer', 1000, function (error, ok) {
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.get(['test1', 'test2', 'test3', 'test4', 'test5'], function (error, answer) {
++callbacks;
assert.ok(typeof answer === 'object');
answer.test1.should.eql('test1answer');
answer.test2.should.eql('test2answer');
answer.should.not.have.key('test3');
answer.should.not.have.key('test4');
answer.should.not.have.key('test5');
memcached.end(); // close connections
assert.equal(callbacks, 3);
done();
});
});
});
});
it('should allow namespacing on delete', function(done) {
var memcached = new Memcached(common.servers.single, {
namespace:'someNamespace:'
}), callbacks = 0;
// put a value
memcached.set('test1', 'test1answer', 1000, function(error, ok) {
callbacks++;
assert.ok(!error);
ok.should.be.true;
// get it back
memcached.get('test1', function(error,answer) {
callbacks++;
assert.ok(typeof answer === 'string');
answer.should.eql('test1answer');
//delete it
memcached.del('test1', function(error) {
callbacks++;
assert.ok(!error);
// no longer there
memcached.get('test1', function(error,answer) {
callbacks++;
assert.ok(!error);
assert.ok(!answer);
memcached.end();
assert.equal(callbacks,4);
done();
});
});
});
});
});
it('should allow increment and decrement on namespaced values', function(done) {
var memcached = new Memcached(common.servers.single, {
namespace:'someNamespace:'
}), callbacks = 0;
// put a value
memcached.set('test1', 1, 1000, function(error, ok) {
callbacks++;
assert.ok(!error);
ok.should.be.true;
// increment it
memcached.incr('test1', 1, function(error) {
callbacks++;
assert.ok(!error);
// get it back
memcached.get('test1', function(error,answer) {
callbacks++;
assert.ok(!error);
assert.ok(typeof answer === 'number');
answer.should.be.eql(2);
// decrement it
memcached.decr('test1', 1, function(err) {
callbacks++;
assert.ok(!error);
// get it again
memcached.get('test1',function(error,answer) {
callbacks++;
assert.ok(!error);
assert.ok(typeof answer === 'number');
answer.should.be.eql(1);
//get rid of it
memcached.del('test1', function(error,answer) {
callbacks++;
assert.ok(!error);
memcached.end();
assert.equal(callbacks,6);
done();
});
});
});
});
});
});
});
});
'use strict';
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
, Memcached = require('../');
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed();
/**
* Expresso test suite for all `parser` related
* memcached commands
*/
describe('Memcached parser', function() {
it('chunked response', function (done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, chunks = []
, chunk = 'VALUE tests::#{key} 2 {length}'
, chunkJSON = JSON.stringify({
lines: []
, message: message
, id: null
})
, testnr = ++global.testnumbers
, callbacks = 0;
// Build up our tests
var S = {
responseBuffer: ''
, bufferArray: []
, metaData: []
, streamID: 0
};
// Build up our chunk data
chunks.push(chunk.replace('{key}', 1).replace('{length}', chunkJSON.length));
chunks.push(chunkJSON);
chunks.push(chunk.replace('{key}', 2).replace('{length}', chunkJSON.length));
// Insert first chunk
memcached.buffer(S, chunks.join('\r\n') +'\r\n');
// We check for bufferArray length otherwise it will crash on 'SyntaxError: Unexpected token V'
assert.equal(S.bufferArray.length, 3);
// Now add the value of the last response key in previous chunk
chunks.unshift(chunkJSON);
// Add it for the second chunk also
chunks.push(chunkJSON);
// Insert second chunk
memcached.buffer(S, chunks.join('\r\n') + '\r\nEND\r\n');
// Check if everything is cleared up nicely.
assert.equal(S.responseBuffer.length, 0);
assert.equal(S.bufferArray.length, 0);
assert.equal(S.metaData.length, 0);
memcached.end();
done();
});
});
/**
* Test dependencies
*/
var assert = require('assert')
, fs = require('fs')
, common = require('./common')
, Memcached = require('../');
global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed();
/**
* Expresso test suite for all `touch` related
* memcached commands
*/
describe("Memcached TOUCH", function() {
/**
* Make sure that touching a key with 1 sec lifetime and getting it 1.1 sec after invoke deletion
*/
it("changes lifetime", function(done) {
var memcached = new Memcached(common.servers.single)
, message = common.alphabet(256)
, testnr = ++global.testnumbers
, callbacks = 0;
memcached.set("test:" + testnr, message, 1, function(error, ok){
++callbacks;
assert.ok(!error);
ok.should.be.true;
memcached.touch("test:" + testnr, 1, function(error, ok){
++callbacks;
assert.ok(!error);
ok.should.be.true;
setTimeout(function(){
memcached.get("test:" + testnr, function(error, answer){
++callbacks;
assert.ok(!error);
assert.ok(answer===undefined);
memcached.end(); // close connections
assert.equal(callbacks, 3);
done();
})}, 1100); // 1.1 sec after
});
});
});
});