diff --git a/.gitignore b/.gitignore index 3c3629e..f1066ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ node_modules +/.idea/ diff --git a/lib/memcached.js b/lib/memcached.js index 1aa96d1..1298e3a 100644 --- a/lib/memcached.js +++ b/lib/memcached.js @@ -125,7 +125,7 @@ Client.config = { // will receive the connection if the operation was successful memcached.connect = function connect(server, callback) { // Default port to 11211 - if(!server.match(/(.+):(\d+)$/)) { + if(server[0] !== '/' && !server.match(/(.+):(\d+)$/)) { server = server + ':11211'; } @@ -556,7 +556,7 @@ Client.config = { } , 'VERSION': function version(tokens, dataSet) { - var versionTokens = /(\d+)(?:\.)(\d+)(?:\.)(\d+)$/.exec(tokens[1]); + var versionTokens = /^(\d+)(?:\.)(\d+)(?:\.)(\d+)/.exec(tokens[1]); return [CONTINUE, { server: this.serverAddress diff --git a/test/common.js b/test/common.js index b6e5cb1..57d4bf5 100644 --- a/test/common.js +++ b/test/common.js @@ -14,16 +14,17 @@ * @type {Object} * @api public */ -var testMemcachedHost = process.env.MEMCACHED__HOST || '127.0.0.1'; - -require('should'); +var testMemcachedHost = process.env.MEMCACHED__HOST || '10.211.55.5'; +var testMemcachedSocketPath = process.env.MEMCACHED__SOCKET_PATH; exports.servers = { - single: testMemcachedHost + ':11211' + single: testMemcachedHost + ':11211', + singleSocket: testMemcachedSocketPath , multi: [ testMemcachedHost + ':11211' , testMemcachedHost + ':11212' , testMemcachedHost + ':11213' + , testMemcachedSocketPath ] }; diff --git a/test/memcached-add.test.js b/test/memcached-add.test.js index cf8112d..d65bff1 100644 --- a/test/memcached-add.test.js +++ b/test/memcached-add.test.js @@ -6,7 +6,9 @@ var assert = require('assert') , fs = require('fs') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-cas.test.js b/test/memcached-cas.test.js index 02d5021..e9868cb 100644 --- a/test/memcached-cas.test.js +++ b/test/memcached-cas.test.js @@ -5,7 +5,9 @@ */ var assert = require('assert') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-connections.test.js b/test/memcached-connections.test.js index c790a2f..c156a71 100644 --- a/test/memcached-connections.test.js +++ b/test/memcached-connections.test.js @@ -10,7 +10,9 @@ var assert = require('assert') , fs = require('fs') , net = require('net') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-get-set-socket.test.js b/test/memcached-get-set-socket.test.js new file mode 100644 index 0000000..07d5003 --- /dev/null +++ b/test/memcached-get-set-socket.test.js @@ -0,0 +1,630 @@ +/** + * Test dependencies + */ + +var assert = require('assert') + , fs = require('fs') + , common = require('./common') + , Memcached = require('../') + , should = require('should') +; + +global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); + +/** + * Expresso test suite for all `get` related + * memcached commands + */ +describe("Memcached GET SET with single Unix Socket", 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 and get a regular string", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = common.alphabet(256) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + + }); + }); + }); + + it("set and get an empty string", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, "", 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(""); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + + }); + }); + }); + + /** + * Set a stringified JSON object, and make sure we only return a string + * this should not be flagged as JSON object + */ + it("set and get a JSON.stringify string", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = JSON.stringify({numbers:common.numbers(256),alphabet:common.alphabet(256),dates:new Date(),arrays: [1,2,3, 'foo', 'bar']}) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + + }); + }); + }); + + /** + * Setting and getting a unicode value should just work, we need to make sure + * that we send the correct byteLength because utf8 chars can contain more bytes + * than "str".length would show, causing the memcached server to complain. + */ + it("set and get a regular string", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = 'привет мир, Memcached и nodejs для победы' + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * A common action when working with memcached servers, getting a key + * that does not exist anymore. + */ + it("get a non existing key", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + assert.ok(answer===undefined); + + memcached.end(); // close connections + assert.equal(callbacks, 1); + done(); + }); + }); + + /** + * Make sure that Numbers are correctly send and stored on the server + * retrieval of the number based values can be tricky as the client might + * think that it was a INCR and not a SET operation.. So just to make sure.. + */ + it("set and get a regular number", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = common.numbers(256) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'number'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Objects should be converted to a JSON string, send to the server + * and be automagically JSON.parsed when they are retrieved. + */ + it("set and get a object", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = { + numbers: common.numbers(256) + , alphabet: common.alphabet(256) + , dates: new Date() + , arrays: [1,2,3, 'foo', 'bar'] + } + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(!Array.isArray(answer) && typeof answer === 'object'); + assert.ok(JSON.stringify(message) === JSON.stringify(answer)); + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Arrays should be converted to a JSON string, send to the server + * and be automagically JSON.parsed when they are retrieved. + */ + it("set and get a array", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = [{ + numbers: common.numbers(256) + , alphabet: common.alphabet(256) + , dates: new Date() + , arrays: [1,2,3, 'foo', 'bar'] + }, { + numbers: common.numbers(256) + , alphabet: common.alphabet(256) + , dates: new Date() + , arrays: [1,2,3, 'foo', 'bar'] + }] + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(Array.isArray(answer)); + assert.ok(JSON.stringify(answer) === JSON.stringify(message)); + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Buffers are commonly used for binary transports So we need to make sure + * we support them properly. But please note, that we need to compare the + * strings on a "binary" level, because that is the encoding the Memcached + * client will be using, as there is no indication of what encoding the + * buffer is in. + */ + it("set and get with a binary image", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = fs.readFileSync(__dirname + '/fixtures/hotchicks.jpg') + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + assert.ok(answer.toString('binary') === message.toString('binary')); + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Get binary of the lipsum.txt, send it over the connection and see + * if after we retrieved it, it's still the same when we compare the + * original with the memcached based version. + * + * A use case for this would be storing with HTML data in + * memcached as a single cache pool.. + */ + it("set and get with a binary text file", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt') + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + assert.ok(answer.toString('utf8') === answer.toString('utf8')); + assert.ok(answer.toString('ascii') === answer.toString('ascii')); + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Set maximum amount of data (1MB), should trigger error, not crash. + */ + it("set maximum data and check for correct error handling", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt').toString() + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, new Array(100).join(message), 1000, function(error, ok){ + ++callbacks; + + assert.equal(error, 'Error: The length of the value is greater than 1048576'); + ok.should.be.false; + + memcached.end(); // close connections + assert.equal(callbacks, 1); + done(); + }); + }); + + /** + * Not only small strings, but also large strings should be processed + * without any issues. + */ + it("set and get large text files", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = fs.readFileSync(__dirname + '/fixtures/lipsum.txt', 'utf8') + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * A multi get on a single server is different than a multi server multi get + * as a multi server multi get will need to do a multi get over multiple servers + * yes, that's allot of multi's in one single sentence thanks for noticing + */ + it("multi get single server", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = common.alphabet(256) + , message2 = common.alphabet(256) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test1:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.set("test2:" + testnr, message2, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get(["test1:" + testnr, "test2:" + testnr], function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'object'); + answer["test1:" + testnr].should.eql(message); + answer["test2:" + testnr].should.eql(message2); + + memcached.end(); // close connections + assert.equal(callbacks, 3); + done(); + }); + }); + }); + }); + + /** + * A multi get on a single server is different than a multi server multi get + * as a multi server multi get will need to do a multi get over multiple servers + * yes, that's allot of multi's in one single sentence thanks for noticing + */ + it("multi get multi server", function(done) { + var memcached = new Memcached(common.servers.multi) + , message = common.alphabet(256) + , message2 = common.alphabet(256) + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test1:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.set("test2:" + testnr, message2, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get(["test1:" + testnr,"test2:" + testnr], function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'object'); + answer["test1:" + testnr].should.eql(message); + answer["test2:" + testnr].should.eql(message2); + + memcached.end(); // close connections + assert.equal(callbacks, 3); + done(); + }); + }); + }); + }); + + /** + * Make sure that a string beginning with OK is not interpreted as + * a command response. + */ + it("set and get a string beginning with OK", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = 'OK123456' + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Make sure that a string beginning with OK is not interpreted as + * a command response. + */ + it("set and get a string beginning with VALUE", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = 'VALUE hello, I\'m not really a value.' + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Make sure that a string containing line breaks are escaped and + * unescaped correctly. + */ + it("set and get a string with line breaks", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = '1\n2\r\n3\n\r4\\n5\\r\\n6\\n\\r7' + , testnr = ++global.testnumbers + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Make sure long keys are hashed + */ + it("make sure you can get really long strings", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = 'VALUE hello, I\'m not really a value.' + , testnr = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"+(++global.testnumbers) + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(!error); + ok.should.be.true; + + memcached.get("test:" + testnr, function(error, answer){ + ++callbacks; + + assert.ok(!error); + + assert.ok(typeof answer === 'string'); + answer.should.eql(message); + + memcached.end(); // close connections + assert.equal(callbacks, 2); + done(); + }); + }); + }); + + /** + * Make sure keys with spaces return an error + */ + it("errors on spaces in strings", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = 'VALUE hello, I\'m not really a value.' + , testnr = " "+(++global.testnumbers) + , callbacks = 0; + + memcached.set("test:" + testnr, message, 1000, function(error, ok){ + ++callbacks; + + assert.ok(error); + assert.ok(error.message === 'The key should not contain any whitespace or new lines'); + + done(); + }); + }); + + /* + Make sure that getMulti calls work for very long keys. + If the keys aren't hashed because they are too long, memcached will throw exceptions, so we need to make sure that exceptions aren't thrown. + */ + it("make sure you can getMulti really long keys", function(done) { + var memcached = new Memcached(common.servers.singleSocket) + , message = 'My value is not relevant' + , testnr1 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"+(++global.testnumbers) + , testnr2 = "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"+(global.testnumbers)+"a" + , callbacks = 0; + + memcached.getMulti([ testnr1, testnr2 ], function(error, ok) { + ++callbacks; + + assert.ok(!error); + memcached.end(); + assert.equal(callbacks, 1); + done(); + }); + }); +}); diff --git a/test/memcached-get-set.test.js b/test/memcached-get-set.test.js index 2614ebf..f993584 100644 --- a/test/memcached-get-set.test.js +++ b/test/memcached-get-set.test.js @@ -5,7 +5,9 @@ var assert = require('assert') , fs = require('fs') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-incr-decr.test.js b/test/memcached-incr-decr.test.js index bbe9dd1..374e15f 100644 --- a/test/memcached-incr-decr.test.js +++ b/test/memcached-incr-decr.test.js @@ -4,7 +4,9 @@ var assert = require('assert') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-namespace.test.js b/test/memcached-namespace.test.js index ea43334..f6b96eb 100644 --- a/test/memcached-namespace.test.js +++ b/test/memcached-namespace.test.js @@ -5,7 +5,9 @@ */ var assert = require('assert') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-parser.test.js b/test/memcached-parser.test.js index 1c19c17..936eef1 100644 --- a/test/memcached-parser.test.js +++ b/test/memcached-parser.test.js @@ -6,7 +6,9 @@ var assert = require('assert') , fs = require('fs') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed(); diff --git a/test/memcached-touch.test.js b/test/memcached-touch.test.js index 1621f23..947fee8 100644 --- a/test/memcached-touch.test.js +++ b/test/memcached-touch.test.js @@ -5,7 +5,9 @@ var assert = require('assert') , fs = require('fs') , common = require('./common') - , Memcached = require('../'); + , Memcached = require('../') + , should = require('should') +; global.testnumbers = global.testnumbers || +(Math.random(10) * 1000000).toFixed();