medea
Version:
A lightweight key-value storage library.
534 lines (472 loc) • 14.3 kB
JavaScript
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var medea = require('../');
var DataBuffer = require('../data_buffer');
var directory = __dirname + '/tmp/medea_test';
var db;
describe('Medea', function() {
before(function(done) {
require('rimraf')(directory, function () {
db = medea({ maxFileSize: 1024*1024 });
db.open(directory, function(err) {
done();
});
})
});
it('has a max file size', function() {
var db = medea();
assert(db.maxFileSize > 0);
});
describe('initialize', function () {
it('can be created as a factory', function () {
assert(medea() instanceof medea);
});
it('successfully created as a Class', function () {
assert((new medea()) instanceof medea);
})
});
describe('#put', function() {
it('successfully stores a String value', function(done) {
db.put('hello', 'world', function(err) {
assert(!err);
done();
});
});
it('successfully stores a Buffer value', function (done) {
db.put('foo', new Buffer('bar'), function(err) {
assert(!err);
done();
});
});
});
describe('#get', function() {
it('successfully retrieves a value', function(done) {
db.put('hello', 'world', function(err) {
db.get('hello', function(err, val) {
assert.equal(val.toString(), 'world');
done();
});
});
});
it('successfully retrieves a UTF-8 value', function(done) {
db.put('hello', '\u2661', function(err) {
db.get('hello', function(err, val) {
assert.equal(val.toString(), '\u2661');
done();
});
});
});
it('successfully retrieves a value', function(done) {
db.put('foo', new Buffer('bar'), function(err) {
db.get('foo', function(err, val) {
assert.equal(val.toString(), 'bar');
done();
});
});
});
it('unsuccessfully retrieves a value', function(done) {
db.put('foo', new Buffer('bar'), function(err) {
db.get('bar', function(err, val) {
assert(!err);
assert(!val);
done();
});
});
});
it('unsuccessfully retrieves a value reserved key name', function(done) {
db.put('foo', new Buffer('bar'), function(err) {
db.get('constructor', function(err, val) {
assert(!err);
assert(!val);
done();
});
});
});
it('successfully recovers from zero-length entries', function(done) {
var db;
var directory = __dirname + '/tmp/medea_test2';
require('rimraf')(directory, function () {
db = medea({ maxFileSize: 1024*1024 });
db.open(directory, function(err) {
db.put('foo', new Buffer('bar'), function(err) {
assert(!err);
db.close(function(err) {
assert(!err);
fs.unlinkSync(path.join(directory, '1.medea.hint'));
var contents = fs.readFileSync(path.join(directory, '1.medea.data'));
var empty = new Buffer(18);
empty.fill(0)
var buf = Buffer.concat([empty, contents]);
var fd = fs.openSync(path.join(directory, '1.medea.data'), 'w+');
fs.write(fd, buf, 0, buf.length, 0, function(err) {
fs.closeSync(fd);
db.open(directory, function(err) {
assert(!err);
db.get('foo', function(err, val) {
assert(!err);
assert.equal(val.toString(), 'bar');
done();
});
});
});
});
});
});
})
});
});
describe('#remove', function() {
it('successfully removes a value', function(done) {
db.put('hello', 'world', function(err) {
db.remove('hello', function(err) {
assert(!err);
done();
});
});
});
});
describe('#write', function() {
it('supports single put operations', function(done) {
var batch = db.createBatch();
batch.put('hello', 'world');
db.write(batch, function() {
db.get('hello', function(err, val) {
assert.equal(val.toString(), 'world');
done();
});
});
});
it('supports single remove operations', function(done) {
db.put('hello','world', function() {
var batch = db.createBatch();
batch.remove('hello');
db.write(batch, function() {
assert.equal(db.keydir.hello, undefined);
db.get('hello', function(err, val) {
assert(!val);
done();
});
});
});
});
it('supports both put and remove operations', function(done) {
db.put('hello','world', function() {
var batch = db.createBatch();
batch.put('hello2', 'world2');
batch.remove('hello');
db.write(batch, function() {
db.get('hello2', function(err, val) {
assert.equal(val.toString(), 'world2');
db.get('hello', function(err, val) {
assert(!val);
done();
});
});
});
});
});
});
describe('#sync', function() {
it('successfully fsync()\'s files', function(done) {
db.sync(function(err) {
assert(!err);
done();
});
});
});
describe('#listKeys', function() {
it('returns keys', function(done) {
db.put('hello$', 'world', function(err) {
db.listKeys(function(err, arr) {
assert(arr.indexOf('hello$') > -1);
done();
});
});
});
});
describe('#mapReduce', function() {
it('successfully maps values', function(done) {
db.put('yello$', 'world', function(err) {
var map = function(key, value, emit) {
emit('value', 1);
};
var reduce = function(key, values) {
if (key == 'value') {
return [values.length];
}
};
var options = { map: map, reduce: reduce };
db.mapReduce(options, function(values) {
assert(values[0] > 0);
done();
});
});
});
});
describe('#compact', function() {
it('compacts successfully', function(done) {
db.put('boyoh', 'butcher', function(err) {
db.compact(function(err) {
assert(!err);
done();
});
});
});
});
describe('#createSnapshot', function () {
var snapshot
before(function (done) {
db.put('beep', 'boop', function () {
db.put('beep2', 'boop2', function () {
snapshot = db.createSnapshot()
db.remove('beep', function () {
db.put('beep2', 'bong', done);
});
});
});
});
describe('#get', function () {
it('successfully retrieves a value', function (done) {
db.get('beep', snapshot, function (err, value) {
assert.equal(value.toString(), 'boop');
db.get('beep2', snapshot, function (err, value2) {
assert.equal(value2.toString(), 'boop2');
done();
});
});
});
describe('not using snapshot', function () {
it('successfully retrieves a value', function (done) {
db.get('beep', function (err, value) {
assert.equal(value, undefined);
db.get('beep2', function (err, value2) {
assert.equal(value2.toString(), 'bong');
done();
});
});
});
});
});
describe('#compact', function (done) {
before(function (done) {
db.compact(done);
});
describe('#get', function () {
it('successfully retrieves a value', function (done) {
db.get('beep', snapshot, function (err, value) {
assert(!err, 'should not Error');
assert.equal(value.toString(), 'boop');
db.get('beep2', snapshot, function (err, value2) {
assert.equal(value2.toString(), 'boop2');
done();
});
});
});
});
});
describe('snapshot#close', function (done) {
before(function (done) {
snapshot.close();
done();
});
describe('#get', function () {
it('returns error about snapshot being closed', function (done) {
db.get('beep', snapshot, function (err) {
assert(err instanceof Error);
assert.equal(err.message, 'Snapshot is closed');
done();
});
});
});
});
});
it('successfully writes large amounts of data', function(done) {
var max = 5000;
var put = function(index) {
if (index === max) {
return done();
}
var key = new Buffer(500);
var val = new Buffer(500);
key.fill(index.toString());
val.fill('v');
db.put(key, val, function(err) {
assert(!err);
put(++index);
});
}
put(0);
});
it('successfully concurrently writes large amounts of data', function(done) {
var max = 5000;
var finished = 0;
for(var index = 0; index < max; ++index) {
var key = new Buffer(500);
var val = new Buffer(500);
key.fill(index.toString());
val.fill('v');
db.put(key, val, function (err) {
assert(!err);
finished++;
if (finished === max) {
return done();
}
});
}
});
it('successfully concurrently reads large amounts of data', function (done) {
var max = 5000;
var finished = 0;
for(var index = 0; index < max; ++index) {
var key = new Buffer(500);
var val = new Buffer(500);
key.fill(index.toString());
db.get(key, function (err) {
assert(!err);
finished++;
if (finished === max) {
return done();
}
});
}
})
after(function(done) {
db.close(done);
});
});
describe('Medea#open() when there are empty hint & data-files', function () {
var db;
directory += 'empty_hint_data_files';
before(function (done) {
require('rimraf')(directory, function () {
fs.mkdir(directory, function () {
fs.open(path.join(directory, '999.medea.data'), 'w', function (err, fd) {
assert(!err);
fs.close(fd, function () {
fs.open(path.join(directory, '999.medea.hint'), 'w', function (err, fd ) {
assert(!err);
fs.close(fd, function () {
db = medea({});
db.open(directory, done);
});
});
});
});
});
});
});
it('successfully remove the empty files', function (done) {
fs.readdir(directory, function (err, files) {
assert.deepEqual(files.indexOf('999.medea.data'), -1)
assert.deepEqual(files.indexOf('999.medea.hint'), -1)
done()
});
});
after(function (done) {
db.close(done);
});
});
describe('Medea#open() and then directly #close()', function () {
it('should not error', function (done) {
db = medea({});
db.open(directory, function(err) {
db.close(done)
});
});
});
describe('Medea#open on already opened directory', function () {
before(function (done) {
db = medea({});
db.open(directory, done);
});
it('should error', function (done) {
var db2 = medea({});
db2.open(directory, function (err) {
assert(err);
done();
});
});
after(function (done) {
db.close(done);
});
});
describe('Medea#open() when there are missing hint files', function () {
it('should pass', function (done) {
var db1 = medea({});
require('rimraf')(directory, function() {
db1.open(directory, function(err) {
assert(!err);
db1.put('hello', 'world', function(err) {
assert(!err);
db1.close(function(err) {
assert(!err);
fs.unlink(directory + '/1.medea.hint', function() {
var db2 = medea({});
db2.open(directory, function(err) {
assert(!err);
// error is thrown before executing this callback
db2.get('hello', function(err, val) {
assert(!err);
assert.equal(val.toString(), 'world');
done();
});
});
});
});
});
});
});
});
it('should pass with leading ./ in directory name and no hint file', function (done) {
var directory = './test/tmp/medea_test';
var db1 = medea({});
require('rimraf')(directory, function() {
db1.open(directory, function(err) {
assert(!err);
db1.put('hello', 'world', function(err) {
assert(!err);
db1.close(function(err) {
assert(!err);
fs.unlink(directory + '/1.medea.hint', function() {
var db2 = medea({});
db2.open(directory, function(err) {
assert(!err);
// error is thrown before executing this callback
db2.get('hello', function(err, val) {
assert(!err);
assert.equal(val.toString(), 'world');
done();
});
});
});
});
});
});
});
});
it('should pass with leading ./ in directory name', function (done) {
var directory = './test/tmp/medea_test';
var db1 = medea({});
require('rimraf')(directory, function() {
db1.open(directory, function(err) {
assert(!err);
db1.put('hello', 'world', function(err) {
assert(!err);
db1.close(function(err) {
assert(!err);
var db2 = medea({});
db2.open(directory, function(err) {
assert(!err);
db2.get('hello', function(err, val) {
assert(!err);
assert.equal(val.toString(), 'world');
done();
});
});
});
});
});
});
});
});