diff --git a/.npmignore b/.npmignore index 1a9df22f..41d9367c 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,4 @@ test -buster.js .airtap.yml .travis.yml .dntrc diff --git a/buster.js b/buster.js deleted file mode 100644 index 28b04f46..00000000 --- a/buster.js +++ /dev/null @@ -1,7 +0,0 @@ -var config = module.exports - -config.unit = { - environment: 'node', - tests: ['test/*-test.js'], - libs: ['test/common.js'] -} diff --git a/package.json b/package.json index b3e9d40e..3eb0929a 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "test-browsers": "airtap --loopback airtap.local test/index.js", "test-browser-local": "airtap --local test/index.js", "hallmark": "hallmark --fix", - "dependency-check": "dependency-check . buster.js test/*.js", + "dependency-check": "dependency-check . test/*.js", "prepublishOnly": "npm run dependency-check" }, "dependencies": { - "deferred-leveldown": "~5.2.0", + "deferred-leveldown": "~5.2.1", "level-errors": "~2.0.0", "level-iterator-stream": "~4.0.0", "xtend": "~4.0.0" @@ -23,9 +23,7 @@ "after": "^0.8.2", "airtap": "^2.0.0", "async-each": "^1.0.3", - "bl": "^3.0.0", "browserify": "^16.0.0", - "bustermove": "^1.0.0", "concat-stream": "^2.0.0", "coveralls": "^3.0.2", "delayed": "^2.0.0", @@ -38,10 +36,11 @@ "memdown": "^5.0.0", "nyc": "^14.0.0", "pinkie": "^2.0.4", - "referee": "^1.2.0", "run-parallel": "^1.1.9", "run-series": "^1.1.8", "safe-buffer": "^5.1.0", + "simple-concat": "^1.0.0", + "sinon": "^7.4.2", "standard": "^14.1.0", "tape": "^4.7.0", "trickle": "0.0.2" diff --git a/test/argument-checking-test.js b/test/argument-checking-test.js index 1017be50..35023de4 100644 --- a/test/argument-checking-test.js +++ b/test/argument-checking-test.js @@ -1,61 +1,37 @@ -var common = require('./common') -var assert = require('referee').assert -var buster = require('bustermove') - -buster.testCase('Argument checking', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'test get() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.get.bind(db), - { name: 'ReadError', message: 'get() requires a key argument' }, - 'no-arg get() throws' - ) - done() - }) - }, - - 'test put() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.put.bind(db), - { name: 'WriteError', message: 'put() requires a key argument' }, - 'no-arg put() throws' - ) - - done() - }) - }, - - 'test del() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.del.bind(db), - { name: 'WriteError', message: 'del() requires a key argument' }, - 'no-arg del() throws' - ) - - done() - }) - }, - - 'test batch() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.batch.bind(db, null, {}), - { name: 'WriteError', message: 'batch() requires an array argument' }, - 'no-arg batch() throws' - ) - - assert.exception( - db.batch.bind(db, {}), - { name: 'WriteError', message: 'batch() requires an array argument' }, - '1-arg, no Array batch() throws' - ) - - done() - }) - } -}) +module.exports = function (test, testCommon) { + test('argument checking', function (t) { + var db = testCommon.factory() + + t.throws( + db.get.bind(db), + /^ReadError: get\(\) requires a key argument$/, + 'no-arg get() throws' + ) + + t.throws( + db.put.bind(db), + /^WriteError: put\(\) requires a key argument$/, + 'no-arg put() throws' + ) + + t.throws( + db.del.bind(db), + /^WriteError: del\(\) requires a key argument$/, + 'no-arg del() throws' + ) + + t.throws( + db.batch.bind(db, null, {}), + /^WriteError: batch\(\) requires an array argument$/, + 'null-arg batch() throws' + ) + + t.throws( + db.batch.bind(db, {}), + /^WriteError: batch\(\) requires an array argument$/, + '1-arg, no array batch() throws' + ) + + db.close(t.end.bind(t)) + }) +} diff --git a/test/batch-test.js b/test/batch-test.js index 0f08f2ce..ecbe562c 100644 --- a/test/batch-test.js +++ b/test/batch-test.js @@ -2,102 +2,97 @@ var levelup = require('../lib/levelup') var errors = levelup.errors var each = require('async-each') var series = require('run-series') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') +var discardable = require('./util/discardable') -buster.testCase('batch()', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'batch() with multiple puts': function (done) { - this.openTestDatabase(function (db) { +module.exports = function (test, testCommon) { + test('array-form batch(): multiple puts', function (t) { + discardable(t, testCommon, function (db, done) { db.batch([ { type: 'put', key: 'foo', value: 'afoovalue' }, { type: 'put', key: 'bar', value: 'abarvalue' }, { type: 'put', key: 'baz', value: 'abazvalue' } ], function (err) { - refute(err) - each(['foo', 'bar', 'baz'], function (key, callback) { + t.ifError(err) + + each(['foo', 'bar', 'baz'], function (key, next) { db.get(key, function (err, value) { - refute(err) - assert.equals(value, 'a' + key + 'value') - callback() + t.ifError(err) + t.is(value, 'a' + key + 'value') + next() }) }, done) }) }) - }, + }) - 'batch() with promise interface': function (done) { - this.openTestDatabase(function (db) { + test('array-form batch(): promise interface', function (t) { + discardable(t, testCommon, function (db, done) { db.batch([ { type: 'put', key: 'foo', value: 'afoovalue' }, { type: 'put', key: 'bar', value: 'abarvalue' }, { type: 'put', key: 'baz', value: 'abazvalue' } ]) .then(function () { - each(['foo', 'bar', 'baz'], function (key, callback) { + each(['foo', 'bar', 'baz'], function (key, next) { db.get(key, function (err, value) { - refute(err) - assert.equals(value, 'a' + key + 'value') - callback() + t.ifError(err) + t.is(value, 'a' + key + 'value') + next() }) }, done) }) .catch(done) }) - }, + }) - 'batch() with multiple puts and deletes': function (done) { - this.openTestDatabase(function (db) { + test('array-form batch(): multiple operations', function (t) { + discardable(t, testCommon, function (db, done) { series([ - function (callback) { + function (next) { db.batch([ { type: 'put', key: '1', value: 'one' }, { type: 'put', key: '2', value: 'two' }, { type: 'put', key: '3', value: 'three' } - ], callback) + ], next) }, - function (callback) { + function (next) { db.batch([ { type: 'put', key: 'foo', value: 'afoovalue' }, { type: 'del', key: '1' }, { type: 'put', key: 'bar', value: 'abarvalue' }, { type: 'del', key: 'foo' }, { type: 'put', key: 'baz', value: 'abazvalue' } - ], callback) + ], next) }, - function (callback) { + function (next) { // these should exist - each(['2', '3', 'bar', 'baz'], function (key, callback) { + each(['2', '3', 'bar', 'baz'], function (key, next) { db.get(key, function (err, value) { - refute(err) - refute.isNull(value) - callback() + t.ifError(err) + t.ok(value != null) + next() }) - }, callback) + }, next) }, - function (callback) { + function (next) { // these shouldn't exist - each(['1', 'foo'], function (key, callback) { + each(['1', 'foo'], function (key, next) { db.get(key, function (err, value) { - assert(err) - assert.isInstanceOf(err, errors.NotFoundError) - refute(value) - callback() + t.ok(err) + t.ok(err instanceof errors.NotFoundError) + t.is(value, undefined) + next() }) - }, callback) + }, next) } ], done) }) - }, + }) - 'batch() with chained interface': function (done) { - this.openTestDatabase(function (db) { + test('chained batch(): multiple operations', function (t) { + discardable(t, testCommon, function (db, done) { db.put('1', 'one', function (err) { - refute(err) + t.ifError(err) db.batch() .put('one', '1') @@ -109,44 +104,49 @@ buster.testCase('batch()', { .put('3', 'three') .del('3') .write(function (err) { - refute(err) + t.ifError(err) - each(['one', 'three', '1', '2', '3'], function (key, callback) { + each(['one', 'three', '1', '2', '3'], function (key, next) { db.get(key, function (err) { - if (['one', 'three', '1', '3'].indexOf(key) > -1) { assert(err) } else { refute(err) } - callback() + if (['one', 'three', '1', '3'].indexOf(key) > -1) { + t.ok(err) + } else { + t.ifError(err) + } + + next() }) }, done) }) }) }) - }, + }) - 'batch() with chained interface - options': function (done) { - this.openTestDatabase(function (db) { + test('chained batch(): options', function (t) { + discardable(t, testCommon, function (db, done) { var batch = db.batch() var write = batch.batch.write.bind(batch.batch) batch.batch.write = function (options, cb) { - assert.equals(options, { foo: 'bar' }) + t.same(options, { foo: 'bar' }) write(options, cb) } batch.put('one', '1') .write({ foo: 'bar' }, function (err) { - refute(err) + t.ifError(err) done() }) }) - }, + }) - 'batch() with chained promise interface - options': function (done) { - this.openTestDatabase(function (db) { + test('chained batch(): promise interface - options', function (t) { + discardable(t, testCommon, function (db, done) { var batch = db.batch() var write = batch.batch.write.bind(batch.batch) batch.batch.write = function (options, cb) { - assert.equals(options, { foo: 'bar' }) + t.same(options, { foo: 'bar' }) write(options, cb) } @@ -155,12 +155,12 @@ buster.testCase('batch()', { .then(done) .catch(done) }) - }, + }) - 'batch() with chained promise interface': function (done) { - this.openTestDatabase(function (db) { + test('chained batch(): promise interface', function (t) { + discardable(t, testCommon, function (db, done) { db.put('1', 'one', function (err) { - refute(err) + t.ifError(err) db.batch() .put('one', '1') @@ -173,218 +173,211 @@ buster.testCase('batch()', { .del('3') .write() .then(function () { - each(['one', 'three', '1', '2', '3'], function (key, callback) { + each(['one', 'three', '1', '2', '3'], function (key, next) { db.get(key, function (err) { - if (['one', 'three', '1', '3'].indexOf(key) > -1) { assert(err) } else { refute(err) } - callback() + if (['one', 'three', '1', '3'].indexOf(key) > -1) { + t.ok(err) + } else { + t.ifError(err) + } + + next() }) }, done) }) .catch(done) }) }) - }, + }) - 'batch() exposes ops queue length': function (done) { - this.openTestDatabase(function (db) { + test('chained batch(): exposes ops queue length', function (t) { + discardable(t, testCommon, function (db, done) { var batch = db.batch() .put('one', '1') .del('two') .put('three', '3') - assert.equals(batch.length, 3) + t.is(batch.length, 3) batch.clear() - assert.equals(batch.length, 0) + t.is(batch.length, 0) batch .del('1') .put('2', 'two') .put('3', 'three') .del('3') - assert.equals(batch.length, 4) + t.is(batch.length, 4) done() }) - }, - - 'batch() with can manipulate data from put()': function (done) { - // checks encoding and whatnot - this.openTestDatabase(function (db) { - series( - [ - db.put.bind(db, '1', 'one'), - db.put.bind(db, '2', 'two'), - db.put.bind(db, '3', 'three'), - function (callback) { - db.batch([ - { type: 'put', key: 'foo', value: 'afoovalue' }, - { type: 'del', key: '1' }, - { type: 'put', key: 'bar', value: 'abarvalue' }, - { type: 'del', key: 'foo' }, - { type: 'put', key: 'baz', value: 'abazvalue' } - ], callback) - }, - function (callback) { - // these should exist - each(['2', '3', 'bar', 'baz'], function (key, callback) { - db.get(key, function (err, value) { - refute(err) - refute.isNull(value) - callback() - }) - }, callback) - }, - function (callback) { - // these shouldn't exist - each(['1', 'foo'], function (key, callback) { - db.get(key, function (err, value) { - assert(err) - assert.isInstanceOf(err, errors.NotFoundError) - refute(value) - callback() - }) - }, callback) - } - ], done) + }) + + test('array-form batch(): can overwrite data from put()', function (t) { + // checks encoding and whatnot (?) + discardable(t, testCommon, function (db, done) { + series([ + db.put.bind(db, '1', 'one'), + db.put.bind(db, '2', 'two'), + db.put.bind(db, '3', 'three'), + function (next) { + db.batch([ + { type: 'put', key: 'foo', value: 'afoovalue' }, + { type: 'del', key: '1' }, + { type: 'put', key: 'bar', value: 'abarvalue' }, + { type: 'del', key: 'foo' }, + { type: 'put', key: 'baz', value: 'abazvalue' } + ], next) + }, + function (next) { + // these should exist + each(['2', '3', 'bar', 'baz'], function (key, next) { + db.get(key, function (err, value) { + t.ifError(err) + t.ok(value != null) + next() + }) + }, next) + }, + function (next) { + // these shouldn't exist + each(['1', 'foo'], function (key, next) { + db.get(key, function (err, value) { + t.ok(err) + t.ok(err instanceof errors.NotFoundError) + t.is(value, undefined) + next() + }) + }, next) + } + ], done) }) - }, + }) - 'batch() data can be read with get() and del()': function (done) { - this.openTestDatabase(function (db) { + test('array-form batch(): data can be read with get() and del()', function (t) { + discardable(t, testCommon, function (db, done) { series([ - function (callback) { + function (next) { db.batch([ { type: 'put', key: '1', value: 'one' }, { type: 'put', key: '2', value: 'two' }, { type: 'put', key: '3', value: 'three' } - ], callback) + ], next) }, db.del.bind(db, '1', 'one'), - function (callback) { + function (next) { // these should exist - each(['2', '3'], function (key, callback) { + each(['2', '3'], function (key, next) { db.get(key, function (err, value) { - refute(err) - refute.isNull(value) - callback() + t.ifError(err) + t.ok(value != null) + next() }) - }, callback) + }, next) }, - function (callback) { + function (next) { // this shouldn't exist db.get('1', function (err, value) { - assert(err) - assert.isInstanceOf(err, errors.NotFoundError) - refute(value) - callback() + t.ok(err) + t.ok(err instanceof errors.NotFoundError) + t.is(value, undefined) + next() }) } ], done) }) - }, + }) - 'chained batch() arguments': { - setUp: function (done) { - this.openTestDatabase(function (db) { - this.db = db - this.batch = db.batch() - done() - }.bind(this)) - }, - - 'test batch#put() with missing `value`': function () { - // value = undefined - assert.exception(this.batch.put.bind(this.batch, 'foo1'), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'value cannot be `null` or `undefined`') { return false } - return true - }) + test('chained batch() arguments', function (t) { + discardable(t, testCommon, function (db, done) { + var batch = db.batch() - assert.exception(this.batch.put.bind(this.batch, 'foo1', null), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'value cannot be `null` or `undefined`') { return false } - return true - }) - }, - - 'test batch#put() with missing `key`': function () { - // key = undefined - assert.exception(this.batch.put.bind(this.batch, undefined, 'foo1'), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'key cannot be `null` or `undefined`') { return false } - return true - }) + t.test('chained batch() arguments: batch#put() with missing `value`', function (t) { + throws(t, batch.put.bind(batch, 'foo1'), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'value cannot be `null` or `undefined`') + }) - // key = null - assert.exception(this.batch.put.bind(this.batch, null, 'foo1'), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'key cannot be `null` or `undefined`') { return false } - return true + throws(t, batch.put.bind(batch, 'foo1', null), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'value cannot be `null` or `undefined`') + }) + + t.end() }) - }, - - 'test batch#put() with missing `key` and `value`': function () { - // undefined - assert.exception(this.batch.put.bind(this.batch), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'key cannot be `null` or `undefined`') { return false } - return true + + t.test('chained batch() arguments: batch#put() with missing `key`', function (t) { + throws(t, batch.put.bind(batch, undefined, 'foo1'), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'key cannot be `null` or `undefined`') + }) + + throws(t, batch.put.bind(batch, null, 'foo1'), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'key cannot be `null` or `undefined`') + }) + + t.end() }) - // null - assert.exception(this.batch.put.bind(this.batch, null, null), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'key cannot be `null` or `undefined`') { return false } - return true + t.test('chained batch() arguments: batch#put() with missing `key` and `value`', function (t) { + throws(t, batch.put.bind(batch), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'key cannot be `null` or `undefined`') + }) + + throws(t, batch.put.bind(batch, null, null), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'key cannot be `null` or `undefined`') + }) + + t.end() }) - }, - - 'test batch#del() with missing `key`': function () { - // key = undefined - assert.exception(this.batch.del.bind(this.batch, undefined, 'foo1'), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'key cannot be `null` or `undefined`') { return false } - return true + + t.test('chained batch() arguments: batch#del() with missing `key`', function (t) { + throws(t, batch.del.bind(batch, undefined, 'foo1'), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'key cannot be `null` or `undefined`') + }) + + throws(t, batch.del.bind(batch, null, 'foo1'), function (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'key cannot be `null` or `undefined`') + }) + + t.end() }) - // key = null - assert.exception(this.batch.del.bind(this.batch, null, 'foo1'), function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'key cannot be `null` or `undefined`') { return false } - return true + t.test('chained batch() arguments: teardown', function (t) { + t.end() + done() }) - }, - - 'test batch#write() with no callback': function () { - this.batch.write() // should not cause an error with no cb - }, - - 'test batch operations after write()': { - setUp: function (done) { - this.batch.put('foo', 'bar').put('boom', 'bang').del('foo').write(done) - this.verify = function (cb) { - assert.exception(cb, function (err) { - if (err.name !== 'WriteError') { return false } - if (err.message !== 'write() already called on this batch') { return false } - return true - }) - } - }, - - 'test put()': function () { - this.verify(function () { - this.batch.put('whoa', 'dude') - }.bind(this)) - }, - - 'test del()': function () { - this.verify(function () { - this.batch.del('foo') - }.bind(this)) - }, - - 'test clear()': function () { - this.verify(function () { - this.batch.clear() - }.bind(this)) + }) + }) + + test('chained batch(): rejects operations after write()', function (t) { + discardable(t, testCommon, function (db, done) { + function verify (err) { + t.is(err.name, 'WriteError') + t.is(err.message, 'write() already called on this batch') } - } + + var batch = db.batch() + batch.put('foo', 'bar').put('boom', 'bang').del('foo').write(function (err) { + t.ifError(err, 'no batch error') + + throws(t, function () { batch.put('whoa', 'dude') }, verify) + throws(t, function () { batch.del('foo') }, verify) + throws(t, function () { batch.clear() }, verify) + + done() + }) + }) + }) +} + +function throws (t, fn, verify) { + try { + fn() + } catch (err) { + return verify(err) } -}) + + t.fail('did not throw') +} diff --git a/test/binary-test.js b/test/binary-test.js index f4bd78c4..1ce7fa2b 100644 --- a/test/binary-test.js +++ b/test/binary-test.js @@ -1,167 +1,145 @@ var each = require('async-each') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') +var discardable = require('./util/discardable') -buster.testCase('Binary API', { - setUp: function (done) { - common.commonSetUp.call(this, function () { - this.testData = loadData() - done() - }.bind(this)) - }, - - tearDown: common.commonTearDown, - - 'sanity check on test data': function (done) { - assert(Buffer.isBuffer(this.testData)) - checkData(this.testData) - done() - }, - - 'test put() and get() with binary value {valueEncoding:binary}': function (done) { - this.openTestDatabase(function (db) { - db.put('binarydata', this.testData, { valueEncoding: 'binary' }, function (err) { - refute(err) +module.exports = function (test, testCommon) { + test('test put() and get() with binary value {valueEncoding:binary}', function (t) { + discardable(t, testCommon, function (db, done) { + db.put('binarydata', testBuffer(), { valueEncoding: 'binary' }, function (err) { + t.ifError(err) db.get('binarydata', { valueEncoding: 'binary' }, function (err, value) { - refute(err) - assert(value) - checkData(value) + t.ifError(err) + t.ok(value) + t.ok(value.equals(testBuffer())) done() }) }) - }.bind(this)) - }, + }) + }) - 'test put() and get() with binary value {valueEncoding:binary} on createDatabase()': function (done) { - this.openTestDatabase({ valueEncoding: 'binary' }, function (db) { - db.put('binarydata', this.testData, function (err) { - refute(err) + test('test put() and get() with binary value {valueEncoding:binary} in factory', function (t) { + discardable(t, testCommon, { valueEncoding: 'binary' }, function (db, done) { + db.put('binarydata', testBuffer(), function (err) { + t.ifError(err) db.get('binarydata', function (err, value) { - refute(err) - assert(value) - checkData(value) + t.ifError(err) + t.ok(value) + t.ok(value.equals(testBuffer())) done() }) }) - }.bind(this)) - }, + }) + }) - 'test put() and get() with binary key {valueEncoding:binary}': function (done) { - this.openTestDatabase(function (db) { - db.put(this.testData, 'binarydata', { valueEncoding: 'binary' }, function (err) { - refute(err) - db.get(this.testData, { valueEncoding: 'binary' }, function (err, value) { - refute(err) - assert(value instanceof Buffer, 'value is buffer') - assert.equals(value.toString(), 'binarydata') + testCommon.bufferKeys && test('test put() and get() with binary key {valueEncoding:binary}', function (t) { + discardable(t, testCommon, function (db, done) { + db.put(testBuffer(), 'binarydata', { valueEncoding: 'binary' }, function (err) { + t.ifError(err) + db.get(testBuffer(), { valueEncoding: 'binary' }, function (err, value) { + t.ifError(err) + t.ok(value instanceof Buffer, 'value is buffer') + t.is(value.toString(), 'binarydata') done() }) - }.bind(this)) - }.bind(this)) - }, + }) + }) + }) - 'test put() and get() with binary value {keyEncoding:utf8,valueEncoding:binary}': function (done) { - this.openTestDatabase(function (db) { - db.put('binarydata', this.testData, { keyEncoding: 'utf8', valueEncoding: 'binary' }, function (err) { - refute(err) + test('test put() and get() with binary value {keyEncoding:utf8,valueEncoding:binary}', function (t) { + discardable(t, testCommon, function (db, done) { + db.put('binarydata', testBuffer(), { keyEncoding: 'utf8', valueEncoding: 'binary' }, function (err) { + t.ifError(err) db.get('binarydata', { keyEncoding: 'utf8', valueEncoding: 'binary' }, function (err, value) { - refute(err) - assert(value) - checkData(value) + t.ifError(err) + t.ok(value) + t.ok(value.equals(testBuffer())) done() }) }) - }.bind(this)) - }, + }) + }) - 'test put() and get() with binary value {keyEncoding:utf8,valueEncoding:binary} on createDatabase()': function (done) { - this.openTestDatabase({ keyEncoding: 'utf8', valueEncoding: 'binary' }, function (db) { - db.put('binarydata', this.testData, function (err) { - refute(err) + test('test put() and get() with binary value {keyEncoding:utf8,valueEncoding:binary} in factory', function (t) { + discardable(t, testCommon, { keyEncoding: 'utf8', valueEncoding: 'binary' }, function (db, done) { + db.put('binarydata', testBuffer(), function (err) { + t.ifError(err) db.get('binarydata', function (err, value) { - refute(err) - assert(value) - checkData(value) + t.ifError(err) + t.ok(value) + t.ok(value.equals(testBuffer())) done() }) }) - }.bind(this)) - }, + }) + }) - 'test put() and get() with binary key {keyEncoding:binary,valueEncoding:utf8}': function (done) { - this.openTestDatabase(function (db) { - db.put(this.testData, 'binarydata', { keyEncoding: 'binary', valueEncoding: 'utf8' }, function (err) { - refute(err) - db.get(this.testData, { keyEncoding: 'binary', valueEncoding: 'utf8' }, function (err, value) { - refute(err) - assert.equals(value, 'binarydata') + testCommon.bufferKeys && test('test put() and get() with binary key {keyEncoding:binary,valueEncoding:utf8}', function (t) { + discardable(t, testCommon, function (db, done) { + db.put(testBuffer(), 'binarydata', { keyEncoding: 'binary', valueEncoding: 'utf8' }, function (err) { + t.ifError(err) + db.get(testBuffer(), { keyEncoding: 'binary', valueEncoding: 'utf8' }, function (err, value) { + t.ifError(err) + t.is(value, 'binarydata') done() }) - }.bind(this)) - }.bind(this)) - }, + }) + }) + }) - 'test put() and get() with binary key & value {valueEncoding:binary}': function (done) { - this.openTestDatabase(function (db) { - db.put(this.testData, this.testData, { valueEncoding: 'binary' }, function (err) { - refute(err) - db.get(this.testData, { valueEncoding: 'binary' }, function (err, value) { - refute(err) - checkData(value) + testCommon.bufferKeys && test('test put() and get() with binary key & value {valueEncoding:binary}', function (t) { + discardable(t, testCommon, function (db, done) { + db.put(testBuffer(), testBuffer(), { valueEncoding: 'binary' }, function (err) { + t.ifError(err) + db.get(testBuffer(), { valueEncoding: 'binary' }, function (err, value) { + t.ifError(err) + t.ok(value.equals(testBuffer())) done() }) - }.bind(this)) - }.bind(this)) - }, + }) + }) + }) - 'test put() and del() and get() with binary key {valueEncoding:binary}': function (done) { - this.openTestDatabase(function (db) { - db.put(this.testData, 'binarydata', { valueEncoding: 'binary' }, function (err) { - refute(err) - db.del(this.testData, { valueEncoding: 'binary' }, function (err) { - refute(err) - db.get(this.testData, { valueEncoding: 'binary' }, function (err, value) { - assert(err) - refute(value) + testCommon.bufferKeys && test('test put() and del() and get() with binary key {valueEncoding:binary}', function (t) { + discardable(t, testCommon, function (db, done) { + db.put(testBuffer(), 'binarydata', { valueEncoding: 'binary' }, function (err) { + t.ifError(err) + db.del(testBuffer(), { valueEncoding: 'binary' }, function (err) { + t.ifError(err) + db.get(testBuffer(), { valueEncoding: 'binary' }, function (err, value) { + t.ok(err) + t.notOk(value) done() }) - }.bind(this)) - }.bind(this)) - }.bind(this)) - }, + }) + }) + }) + }) - 'batch() with multiple puts': function (done) { - this.openTestDatabase(function (db) { + test('batch() with multiple puts', function (t) { + discardable(t, testCommon, function (db, done) { db.batch([ - { type: 'put', key: 'foo', value: this.testData }, - { type: 'put', key: 'bar', value: this.testData }, + { type: 'put', key: 'foo', value: testBuffer() }, + { type: 'put', key: 'bar', value: testBuffer() }, { type: 'put', key: 'baz', value: 'abazvalue' } ], { keyEncoding: 'utf8', valueEncoding: 'binary' }, function (err) { - refute(err) - each(['foo', 'bar', 'baz'], function (key, callback) { + t.ifError(err) + each(['foo', 'bar', 'baz'], function (key, next) { db.get(key, { valueEncoding: 'binary' }, function (err, value) { - refute(err) + t.ifError(err) if (key === 'baz') { - assert(value instanceof Buffer, 'value is buffer') - assert.equals(value.toString(), 'a' + key + 'value') - callback() + t.ok(value instanceof Buffer, 'value is buffer') + t.is(value.toString(), 'a' + key + 'value') + next() } else { - checkData(value) - callback() + t.ok(value.equals(testBuffer())) + next() } }) }, done) }) - }.bind(this)) - } -}) - -function loadData () { - return Buffer.from('0080c0ff', 'hex') + }) + }) } -function checkData (buf) { - assert.equals(buf.equals(loadData()), true) +function testBuffer () { + return Buffer.from('0080c0ff', 'hex') } diff --git a/test/browserify-test.js b/test/browserify-test.js index 10d7681f..8aaa8bfd 100644 --- a/test/browserify-test.js +++ b/test/browserify-test.js @@ -1,48 +1,47 @@ -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') var browserify = require('browserify') var path = require('path') var after = require('after') -var bl = require('bl') +var concat = require('simple-concat') var spawn = require('child_process').spawn var PACKAGE_JSON = path.join(__dirname, '..', 'package.json') -buster.testCase('Browserify Bundle', { - 'does not contain package.json': function (done) { +module.exports = function (test) { + test('does not contain package.json', function (t) { var b = browserify(path.join(__dirname, '..'), { browserField: true }) - .once('error', function (error) { - assert.fail(error) - done() - }) + .once('error', t.end.bind(t)) b.pipeline .on('file', function (file, id, parent) { - refute.equals(file, PACKAGE_JSON) + t.isNot(file, PACKAGE_JSON) }) - b.bundle(done) - }, - 'throws error if missing db factory': function (done) { + b.bundle(t.end.bind(t)) + }) + + test('throws error if missing db factory', function (t) { var b = browserify(path.join(__dirname, 'data/browser-throws.js'), { browserField: true }) var node = spawn('node') - var fin = after(2, done) - node.stderr.pipe(bl(function (err, buf) { - refute(err) - assert.match(buf.toString(), /InitializationError: First argument must be an abstract-leveldown compliant store/) - fin() - })) + var next = after(2, t.end.bind(t)) + + concat(node.stderr, function (err, buf) { + t.ifError(err) + t.ok(/InitializationError: First argument must be an abstract-leveldown compliant store/.test(buf)) + next() + }) + node.on('exit', function (code) { - assert.equals(code, 1) - fin() + t.is(code, 1) + next() }) + b.bundle().pipe(node.stdin) - }, - 'works with valid db factory (memdown)': function (done) { + }) + + test('works with valid db factory (memdown)', function (t) { var b = browserify(path.join(__dirname, 'data/browser-works.js'), { browserField: true }) var node = spawn('node') node.on('exit', function (code) { - assert.equals(code, 0) - done() + t.is(code, 0) + t.end() }) b.bundle().pipe(node.stdin) - } -}) + }) +} diff --git a/test/clear-test.js b/test/clear-test.js index 083df816..c2d7dcd9 100644 --- a/test/clear-test.js +++ b/test/clear-test.js @@ -1,83 +1,84 @@ -var test = require('tape') var memdown = require('memdown') var encode = require('encoding-down') var concat = require('level-concat-iterator') var levelup = require('../lib/levelup') -test('clear()', function (t) { - function makeTest (name, fn) { - t.test(name, function (t) { - var mem = memdown() +module.exports = function (test) { + test('clear()', function (t) { + function makeTest (name, fn) { + t.test(name, function (t) { + var mem = memdown() + + mem.open(function (err) { + t.ifError(err, 'no open error') + + mem.batch([ + { type: 'put', key: '"a"', value: 'a' }, + { type: 'put', key: '"b"', value: 'b' } + ], function (err) { + t.ifError(err, 'no batch error') + + mem.close(function (err) { + t.ifError(err, 'no close error') + fn(t, mem) + }) + }) + }) + }) + } - mem.open(function (err) { - t.ifError(err, 'no open error') + function verify (t, db, expectedKey) { + concat(db.iterator({ keyAsBuffer: false }), function (err, entries) { + t.ifError(err, 'no concat error') + t.same(entries.map(function (e) { return e.key }), [expectedKey], 'got expected keys') + db.close(t.end.bind(t)) + }) + } - mem.batch([ - { type: 'put', key: '"a"', value: 'a' }, - { type: 'put', key: '"b"', value: 'b' } - ], function (err) { - t.ifError(err, 'no batch error') + makeTest('clear() without encoding, without deferred-open', function (t, mem) { + var db = levelup(mem) - mem.close(function (err) { - t.ifError(err, 'no close error') - fn(t, mem) - }) + db.open(function (err) { + t.ifError(err) + + db.clear({ gte: '"b"' }, function (err) { + t.ifError(err, 'no clear error') + verify(t, db, '"a"') }) }) }) - } - function verify (t, db, expectedKey) { - concat(db.iterator({ keyAsBuffer: false }), function (err, entries) { - t.ifError(err, 'no concat error') - t.same(entries.map(function (e) { return e.key }), [expectedKey], 'got expected keys') - db.close(t.end.bind(t)) - }) - } - - makeTest('clear() without encoding, without deferred-open', function (t, mem) { - var db = levelup(mem) - - db.open(function (err) { - t.ifError(err) + makeTest('clear() without encoding, with deferred-open', function (t, mem) { + var db = levelup(mem) db.clear({ gte: '"b"' }, function (err) { t.ifError(err, 'no clear error') verify(t, db, '"a"') }) }) - }) - makeTest('clear() without encoding, with deferred-open', function (t, mem) { - var db = levelup(mem) - - db.clear({ gte: '"b"' }, function (err) { - t.ifError(err, 'no clear error') - verify(t, db, '"a"') - }) - }) + makeTest('clear() with encoding, with deferred-open', function (t, mem) { + var db = levelup(encode(mem, { keyEncoding: 'json' })) - makeTest('clear() with encoding, with deferred-open', function (t, mem) { - var db = levelup(encode(mem, { keyEncoding: 'json' })) - - db.clear({ gte: 'b' }, function (err) { - t.ifError(err, 'no clear error') - verify(t, db, 'a') + db.clear({ gte: 'b' }, function (err) { + t.ifError(err, 'no clear error') + verify(t, db, 'a') + }) }) - }) - makeTest('clear() with encoding, without deferred-open', function (t, mem) { - var db = levelup(encode(mem, { keyEncoding: 'json' })) + makeTest('clear() with encoding, without deferred-open', function (t, mem) { + var db = levelup(encode(mem, { keyEncoding: 'json' })) - db.open(function (err) { - t.ifError(err) + db.open(function (err) { + t.ifError(err) - db.clear({ gte: 'b' }, function (err) { - t.ifError(err, 'no clear error') - verify(t, db, 'a') + db.clear({ gte: 'b' }, function (err) { + t.ifError(err, 'no clear error') + verify(t, db, 'a') + }) }) }) - }) - t.end() -}) + t.end() + }) +} diff --git a/test/common.js b/test/common.js index 1062848f..68cad218 100644 --- a/test/common.js +++ b/test/common.js @@ -1,98 +1,29 @@ -var referee = require('referee') -var assert = referee.assert -var refute = referee.refute -var each = require('async-each') -var delayed = require('delayed').delayed -var levelup = require('../lib/levelup.js') -var errors = require('level-errors') -var memdown = require('memdown') -var encDown = require('encoding-down') - -assert(levelup.errors === errors) - -referee.add('isInstanceOf', { - assert: function (actual, expected) { - return actual instanceof expected - }, - refute: function (actual, expected) { - return !(actual instanceof expected) - }, - assertMessage: '${0} expected to be instance of ${1}', // eslint-disable-line - refuteMessage: '${0} expected not to be instance of ${1}' // eslint-disable-line -}) - -referee.add('isUndefined', { - assert: function (actual) { - return actual === undefined - }, - refute: function (actual) { - return actual !== undefined - }, - assertMessage: '${0} expected to be undefined', // eslint-disable-line - refuteMessage: '${0} expected not to be undefined' // eslint-disable-line -}) - -module.exports.openTestDatabase = function () { - var options = typeof arguments[0] === 'object' ? arguments[0] : {} - var callback = typeof arguments[0] === 'function' ? arguments[0] : arguments[1] - - levelup(encDown(memdown(), options), function (err, db) { - refute(err) - if (!err) { - this.closeableDatabases.push(db) - callback(db) - } - }.bind(this)) -} - -module.exports.commonTearDown = function (done) { - each(this.closeableDatabases, function (db, callback) { - db.close(callback) - }, done) -} - -module.exports.commonSetUp = function (done) { - this.closeableDatabases = [] - this.openTestDatabase = module.exports.openTestDatabase.bind(this) - this.timeout = 10000 - process.nextTick(done) -} - -module.exports.readStreamSetUp = function (done) { - module.exports.commonSetUp.call(this, function () { - var i - var k - - this.dataSpy = this.spy() - this.endSpy = this.spy() - this.sourceData = [] - - for (i = 0; i < 100; i++) { - k = (i < 10 ? '0' : '') + i - this.sourceData.push({ - type: 'put', - key: k, - value: Math.random() - }) - } - - this.verify = delayed(function (rs, done, data) { - if (!data) data = this.sourceData // can pass alternative data array for verification - assert.equals(this.endSpy.callCount, 1, 'ReadStream emitted single "end" event') - assert.equals(this.dataSpy.callCount, data.length, 'ReadStream emitted correct number of "data" events') - data.forEach(function (d, i) { - var call = this.dataSpy.getCall(i) - if (call) { - assert.equals(call.args.length, 1, 'ReadStream "data" event #' + i + ' fired with 1 argument') - refute.isNull(call.args[0].key, 'ReadStream "data" event #' + i + ' argument has "key" property') - refute.isNull(call.args[0].value, 'ReadStream "data" event #' + i + ' argument has "value" property') - assert.equals(call.args[0].key, d.key, 'ReadStream "data" event #' + i + ' argument has correct "key"') - assert.equals(+call.args[0].value, +d.value, 'ReadStream "data" event #' + i + ' argument has correct "value"') - } - }.bind(this)) - done() - }, 0.05, this) - - done() - }.bind(this)) +// Same interface as abstract-leveldown's testCommon +module.exports = function testCommon (options) { + var factory = options.factory + var test = options.test + + if (typeof factory !== 'function') { + throw new TypeError('factory must be a function') + } + + if (typeof test !== 'function') { + throw new TypeError('test must be a function') + } + + return { + test: test, + factory: factory, + + bufferKeys: options.bufferKeys !== false, + // createIfMissing: options.createIfMissing !== false, + // errorIfExists: options.errorIfExists !== false, + snapshots: options.snapshots !== false, + seek: options.seek !== false, + clear: !!options.clear, + deferredOpen: !!options.deferredOpen, + promises: !!options.promises, + streams: !!options.streams, + encodings: !!options.encodings + } } diff --git a/test/create-stream-vs-put-racecondition.js b/test/create-stream-vs-put-racecondition.js index d0f24237..6faab6d4 100644 --- a/test/create-stream-vs-put-racecondition.js +++ b/test/create-stream-vs-put-racecondition.js @@ -1,89 +1,69 @@ -// NOTE: this file is outdated. It is not included in the test suite (index.js). - -var levelup = require('../lib/levelup.js') +var levelup = require('../lib/levelup') var memdown = require('memdown') -var common = require('./common') -var assert = require('referee').assert -var buster = require('bustermove') - -function makeTest (db, delay, done) { - // this should be an empty stream - var i = 0 - var j = 0 - var k = 0 - var m = 0 - var streamEnd = false - var putEnd = false - - db.createReadStream() - .on('data', function (data) { - i++ +var encdown = require('encoding-down') +var after = require('after') + +module.exports = function (test, testCommon) { + ;[true, false].forEach(function (encode) { + ;[true, false].forEach(function (deferredOpen) { + ;[true, false].forEach(function (delayedPut) { + makeTest(test, encode, deferredOpen, delayedPut) + }) }) - .on('end', function () { - // since the readStream is created before inserting anything - // it should be empty? right? - assert.equals(i, 0, 'stream read the future') - - if (putEnd) done() - streamEnd = true - }) - - db.on('put', function (key, value) { - j++ }) +} - // insert 10 things, - // then check the right number of events where emitted. - function insert () { - m++ - db.put('hello' + k++ / 10, k, next) - } +function makeTest (test, encode, deferredOpen, delayedPut) { + var name = [ + 'readStream before put', + encode && 'encode', + deferredOpen && 'deferred open', + delayedPut && 'delayed put' + ].filter(Boolean).join(', ') - delay(function () { - insert(); insert(); insert(); insert(); insert() - insert(); insert(); insert(); insert(); insert() - }) + test(name, function (t) { + var db = encode ? levelup(encdown(memdown())) : levelup(memdown()) + var delay = delayedPut ? process.nextTick : callFn - function next () { - if (--m) return - process.nextTick(function () { - assert.equals(j, 10) - assert.equals(i, 0) + run(t, db, !deferredOpen, delay) + }) +} - if (streamEnd) done() - putEnd = true +function run (t, db, explicitOpen, delay) { + if (explicitOpen) { + return db.open(function (err) { + t.ifError(err, 'no open error') + run(t, db, false, delay) }) } -} - -buster.testCase('ReadStream', { - setUp: common.readStreamSetUp, - tearDown: common.commonTearDown, + var reads = 0 + var next = after(11, function (err) { + t.ifError(err, 'no error') + t.is(reads, 0, 'got 0 items from snaphot') - // TODO: test various encodings - 'readStream and then put in nextTick': function (done) { - this.openTestDatabase(function (db) { - makeTest(db, process.nextTick, done) + db.close(function (err) { + t.ifError(err, 'no close error') + t.end() }) - }, - 'readStream and then put in nextTick, defered open': function (done) { - var db = levelup(memdown()) - - this.closeableDatabases.push(db) + }) - makeTest(db, process.nextTick, done) - }, - 'readStream and then put, defered open': function (done) { - var db = levelup(memdown()) + // Should read from a snapshot, unaffected by later writes, + // even if those are performed in the same tick. + db.createReadStream() + .on('data', function () { + reads++ + }) + .on('end', next) - this.closeableDatabases.push(db) + // Write data + delay(function () { + for (var i = 0; i < 10; i++) { + db.put(String(i), String(i), next) + } + }) +} - makeTest(db, function (f) { f() }, done) - }, - 'readStream and then put': function (done) { - this.openTestDatabase(function (db) { - makeTest(db, function (f) { f() }, done) - }) - } -}) +function callFn (fn) { + fn() +} diff --git a/test/custom-encoding-test.js b/test/custom-encoding-test.js new file mode 100644 index 00000000..a622d661 --- /dev/null +++ b/test/custom-encoding-test.js @@ -0,0 +1,93 @@ +var each = require('async-each') +var discardable = require('./util/discardable') + +module.exports = function (test, testCommon) { + test('custom encoding: simple-object values in "json" encoding', function (t) { + run(t, [ + { key: '0', value: 0 }, + { key: '1', value: 1 }, + { key: 'string', value: 'a string' }, + { key: 'true', value: true }, + { key: 'false', value: false } + ]) + }) + + test('custom encoding: simple-object keys in "json" encoding', function (t) { + // Test keys that would be considered the same with default utf8 encoding. + // Because String([1]) === String(1). + run(t, [ + { value: '0', key: [1] }, + { value: '1', key: 1 }, + { value: 'string', key: 'a string' }, + { value: 'true', key: true }, + { value: 'false', key: false } + ]) + }) + + test('custom encoding: complex-object values in "json" encoding', function (t) { + run(t, [ + { + key: '0', + value: { + foo: 'bar', + bar: [1, 2, 3], + bang: { yes: true, no: false } + } + } + ]) + }) + + test('custom encoding: complex-object keys in "json" encoding', function (t) { + // Test keys that would be considered the same with default utf8 encoding. + // Because String({}) === String({}) === '[object Object]'. + run(t, [ + { + value: '0', + key: { + foo: 'bar', + bar: [1, 2, 3], + bang: { yes: true, no: false } + } + }, + { + value: '1', + key: { + foo: 'different', + bar: [1, 2, 3], + bang: { yes: true, no: false } + } + } + ]) + }) + + function run (t, entries) { + var customEncoding = { + encode: JSON.stringify, + decode: JSON.parse, + buffer: false, + type: 'custom' + } + + discardable(t, testCommon, { + keyEncoding: customEncoding, + valueEncoding: customEncoding + }, function (db, done) { + var ops = entries.map(function (entry) { + return { type: 'put', key: entry.key, value: entry.value } + }) + + db.batch(ops, function (err) { + t.ifError(err) + each(entries, visit, done) + + function visit (entry, next) { + db.get(entry.key, function (err, value) { + t.ifError(err) + t.same(entry.value, value) + next() + }) + } + }) + }) + } +} diff --git a/test/deferred-open-test.js b/test/deferred-open-test.js index 9e419653..3cb9783d 100644 --- a/test/deferred-open-test.js +++ b/test/deferred-open-test.js @@ -1,24 +1,14 @@ -var levelup = require('../lib/levelup.js') -var memdown = require('memdown') -var encDown = require('encoding-down') var each = require('async-each') var parallel = require('run-parallel') var concat = require('concat-stream') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') +var sinon = require('sinon') +var readStreamContext = require('./util/rs-context') -buster.testCase('Deferred open()', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'put() and get() on pre-opened database': function (done) { +module.exports = function (test, testCommon) { + test('deferred open(): put() and get() on new database', function (t) { // 1) open database without callback, opens in next tick - var db = levelup(encDown(memdown())) - - this.closeableDatabases.push(db) - assert.isObject(db) + var db = testCommon.factory() + t.ok(typeof db === 'object' && db !== null) parallel([ // 2) insert 3 values with put(), these should be deferred until the database is actually open @@ -28,32 +18,29 @@ buster.testCase('Deferred open()', { ], function () { // 3) when the callbacks have returned, the database should be open and those values should be in // verify that the values are there - each([1, 2, 3], function (k, cb) { + each([1, 2, 3], function (k, next) { db.get('k' + k, function (err, v) { - refute(err) - assert.equals(v, 'v' + k) - cb() + t.ifError(err) + t.is(v, 'v' + k) + next() }) }, function () { db.get('k4', function (err) { - assert(err) - // DONE - done() + t.ok(err) + db.close(t.end.bind(t)) }) }) }) // we should still be in a state of limbo down here, not opened or closed, but 'new' - refute(db.isOpen()) - refute(db.isClosed()) - }, + t.is(db.isOpen(), false) + t.is(db.isClosed(), false) + }) - 'batch() on pre-opened database': function (done) { + test('deferred open(): batch() on new database', function (t) { // 1) open database without callback, opens in next tick - var db = levelup(encDown(memdown())) - - this.closeableDatabases.push(db) - assert.isObject(db) + var db = testCommon.factory() + t.ok(typeof db === 'object' && db !== null) // 2) insert 3 values with batch(), these should be deferred until the database is actually open db.batch([ @@ -63,32 +50,29 @@ buster.testCase('Deferred open()', { ], function () { // 3) when the callbacks have returned, the database should be open and those values should be in // verify that the values are there - each([1, 2, 3], function (k, cb) { + each([1, 2, 3], function (k, next) { db.get('k' + k, function (err, v) { - refute(err) - assert.equals(v, 'v' + k) - cb() + t.ifError(err) + t.is(v, 'v' + k) + next() }) }, function () { db.get('k4', function (err) { - assert(err) - // DONE - done() + t.ok(err) + db.close(t.end.bind(t)) }) }) }) // we should still be in a state of limbo down here, not opened or closed, but 'new' - refute(db.isOpen()) - refute(db.isClosed()) - }, + t.is(db.isOpen(), false) + t.is(db.isClosed(), false) + }) - 'chained batch() on pre-opened database': function (done) { + test('deferred open(): chained batch() on new database', function (t) { // 1) open database without callback, opens in next tick - var db = levelup(encDown(memdown())) - - this.closeableDatabases.push(db) - assert.isObject(db) + var db = testCommon.factory() + t.ok(typeof db === 'object' && db !== null) // 2) insert 3 values with batch(), these should be deferred until the database is actually open db.batch() @@ -98,61 +82,59 @@ buster.testCase('Deferred open()', { .write(function () { // 3) when the callbacks have returned, the database should be open and those values should be in // verify that the values are there - each([1, 2, 3], function (k, cb) { + each([1, 2, 3], function (k, next) { db.get('k' + k, function (err, v) { - refute(err) - assert.equals(v, 'v' + k) - cb() + t.ifError(err) + t.is(v, 'v' + k) + next() }) }, function () { db.get('k4', function (err) { - assert(err) - // DONE - done() + t.ok(err) + db.close(t.end.bind(t)) }) }) }) // we should still be in a state of limbo down here, not opened or closed, but 'new' - refute(db.isOpen()) - refute(db.isClosed()) - }, - - 'test deferred ReadStream': { - setUp: common.readStreamSetUp, - - 'simple ReadStream': function (done) { - var db = levelup(encDown(memdown())) - db.batch(this.sourceData.slice(), function (err) { - refute(err) - db.close(function (err) { - refute(err, 'no error') - var async = true - - db.open(function (err) { - async = false - refute(err, 'no open error') - }) + t.is(db.isOpen(), false) + t.is(db.isClosed(), false) + }) + + test('deferred open(): test deferred ReadStream', function (t) { + var ctx = readStreamContext(t) + var db = testCommon.factory() + + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + db.close(function (err) { + t.ifError(err, 'no error') + var async = true + + db.open(function (err) { + async = false + t.ifError(err, 'no open error') + }) - this.closeableDatabases.push(db) - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + db.createReadStream() + .on('data', ctx.dataSpy) + .on('end', ctx.endSpy) + .on('close', function () { + ctx.verify() + db.close(t.end.bind(t)) + }) - // db should open lazily - assert(async) - }.bind(this)) - }.bind(this)) - } - }, + // db should open lazily + t.ok(async) + }) + }) + }) - 'maxListeners warning': function (done) { + test('deferred open(): maxListeners warning', function (t) { // 1) open database without callback, opens in next tick - var db = levelup(encDown(memdown())) - var stderrMock = this.mock(console) + var db = testCommon.factory() + var stderrMock = sinon.mock(console) - this.closeableDatabases.push(db) stderrMock.expects('error').never() // 2) provoke an EventEmitter maxListeners warning @@ -160,44 +142,40 @@ buster.testCase('Deferred open()', { for (var i = 0; i < toPut; i++) { db.put('some', 'string', function (err) { - refute(err) + t.ifError(err) if (!--toPut) { - done() + db.close(t.end.bind(t)) } }) } - }, - - 'value of queued operation is not serialized': function (done) { - var db = levelup(encDown(memdown(), { valueEncoding: 'json' })) + }) - this.closeableDatabases.push(db) + test('deferred open(): value of queued operation is not serialized', function (t) { + var db = testCommon.factory({ valueEncoding: 'json' }) // deferred-leveldown < 2.0.2 would serialize the object to a string. db.put('key', { thing: 2 }, function (err) { - refute(err) + t.ifError(err) db.get('key', function (err, value) { - refute(err) - assert.equals(value, { thing: 2 }) - done() + t.ifError(err) + t.same(value, { thing: 2 }) + db.close(t.end.bind(t)) }) }) - }, - - 'key of queued operation is not serialized': function (done) { - var db = levelup(encDown(memdown(), { keyEncoding: 'json' })) + }) - this.closeableDatabases.push(db) + test('deferred open(): key of queued operation is not serialized', function (t) { + var db = testCommon.factory({ keyEncoding: 'json' }) // deferred-leveldown < 2.0.2 would serialize the key to a string. db.put({ thing: 2 }, 'value', function (err) { - refute(err) + t.ifError(err) db.createKeyStream().pipe(concat(function (result) { - assert.equals(result, [{ thing: 2 }]) - done() + t.same(result, [{ thing: 2 }]) + db.close(t.end.bind(t)) })) }) - } -}) + }) +} diff --git a/test/get-put-del-test.js b/test/get-put-del-test.js index a9335585..8e679fbf 100644 --- a/test/get-put-del-test.js +++ b/test/get-put-del-test.js @@ -1,154 +1,119 @@ -var levelup = require('../lib/levelup.js') -var errors = levelup.errors +var errors = require('../lib/levelup').errors var each = require('async-each') var series = require('run-series') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') +var discardable = require('./util/discardable') -buster.testCase('get() / put() / del()', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, +module.exports = function (test, testCommon) { + test('get() / put() / del(): get() on empty database causes error', function (t) { + discardable(t, testCommon, function (db, done) { + db.get('undefkey', function (err, value) { + t.notOk(value) + t.ok(err instanceof Error) + t.ok(err instanceof errors.LevelUPError) + t.ok(err instanceof errors.NotFoundError) + t.is(err.notFound, true, 'err.notFound is `true`') + t.is(err.status, 404, 'err.status is 404') + t.ok(/\[undefkey\]/.test(err)) + done() + }) + }) + }) - 'Simple operations': { - 'get() on empty database causes error': function (done) { - this.openTestDatabase(function (db) { - db.get('undefkey', function (err, value) { - refute(value) - assert.isInstanceOf(err, Error) - assert.isInstanceOf(err, errors.LevelUPError) - assert.isInstanceOf(err, errors.NotFoundError) - assert(err.notFound === true, 'err.notFound is `true`') - assert.equals(err.status, 404, 'err.status is 404') - assert.match(err, '[undefkey]') - done() - }) + testCommon.promises && test('get() / put() / del(): get() on empty database causes error (promise)', function (t) { + discardable(t, testCommon, function (db, done) { + db.get('undefkey').catch(function (err) { + t.ok(err instanceof Error) + t.ok(err instanceof errors.LevelUPError) + t.ok(err instanceof errors.NotFoundError) + t.is(err.notFound, true, 'err.notFound is `true`') + t.is(err.status, 404, 'err.status is 404') + t.ok(/\[undefkey\]/.test(err)) + done() }) - }, + }) + }) - 'get() on empty database raises promise error': function (done) { - this.openTestDatabase(function (db) { - db.get('undefkey').catch(function (err) { - assert.isInstanceOf(err, Error) - assert.isInstanceOf(err, errors.LevelUPError) - assert.isInstanceOf(err, errors.NotFoundError) - assert(err.notFound === true, 'err.notFound is `true`') - assert.equals(err.status, 404, 'err.status is 404') - assert.match(err, '[undefkey]') + test('get() / put() / del(): put() and get() simple string entries', function (t) { + discardable(t, testCommon, function (db, done) { + db.put('some key', 'some value stored in the database', function (err) { + t.ifError(err) + db.get('some key', function (err, value) { + t.ifError(err) + t.is(value, 'some value stored in the database') done() }) }) - }, + }) + }) - 'put() and get() simple string key/value pairs': function (done) { - this.openTestDatabase(function (db) { - db.put('some key', 'some value stored in the database', function (err) { - refute(err) - db.get('some key', function (err, value) { - refute(err) - assert.equals(value, 'some value stored in the database') - done() - }) + testCommon.promises && test('get() / put() / del(): put() and get() simple string entries (promise)', function (t) { + discardable(t, testCommon, function (db, done) { + db.put('some key', 'some value stored in the database') + .then(function () { + return db.get('some key') }) - }) - }, - - 'put() and get() promise interface': function (done) { - this.openTestDatabase(function (db) { - db.put('some key', 'some value stored in the database') - .then(function () { - return db.get('some key') - }) - .then(function (value) { - assert.equals(value, 'some value stored in the database') - done() - }) - .catch(done) - }) - }, - - 'del() on empty database doesn\'t cause error': function (done) { - this.openTestDatabase(function (db) { - db.del('undefkey', function (err) { - refute(err) + .then(function (value) { + t.is(value, 'some value stored in the database') done() }) - }) - }, - - 'del() promise interface': function (done) { - this.openTestDatabase(function (db) { - db.del('undefkey') - .then(done) - .catch(done) - }) - }, + .catch(done) + }) + }) - 'del() works on real entries': function (done) { - this.openTestDatabase(function (db) { - series([ - function (callback) { - each(['foo', 'bar', 'baz'], function (key, callback) { - db.put(key, 1 + Math.random(), callback) - }, callback) - }, - function (callback) { - db.del('bar', callback) - }, - function (callback) { - each(['foo', 'bar', 'baz'], function (key, callback) { - db.get(key, function (err, value) { - // we should get foo & baz but not bar - if (key === 'bar') { - assert(err) - refute(value) - } else { - refute(err) - assert(value) - } - callback() - }) - }, callback) - } - ], done) + test('get() / put() / del(): can del() on empty database', function (t) { + discardable(t, testCommon, function (db, done) { + db.del('undefkey', function (err) { + t.ifError(err) + done() }) - } - }, + }) + }) - 'test get() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.get.bind(db), - { name: 'ReadError', message: 'get() requires a key argument' }, - 'no-arg get() throws' - ) - done() + testCommon.promises && test('get() / put() / del(): can del() on empty database (promise)', function (t) { + discardable(t, testCommon, function (db, done) { + db.del('undefkey') + .then(done) + .catch(done) }) - }, + }) - 'test put() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.put.bind(db), - { name: 'WriteError', message: 'put() requires a key argument' }, - 'no-arg put() throws' - ) + test('get() / put() / del(): del() works on real entries', function (t) { + discardable(t, testCommon, function (db, done) { + series([ + function (next) { + each(['foo', 'bar', 'baz'], function (key, next) { + db.put(key, 1 + Math.random(), next) + }, next) + }, + function (next) { + db.del('bar', next) + }, + function (next) { + each(['foo', 'bar', 'baz'], function (key, next) { + db.get(key, function (err, value) { + // we should get foo & baz but not bar + if (key === 'bar') { + t.ok(err) + t.notOk(value) + } else { + t.ifError(err) + t.ok(value) + } - done() + next() + }) + }, next) + } + ], done) }) - }, - - 'test del() throwables': function (done) { - this.openTestDatabase(function (db) { - assert.exception( - db.del.bind(db), - { name: 'WriteError', message: 'del() requires a key argument' }, - 'no-arg del() throws' - ) + }) + test('get() / put() / del(): throw if no key is provided', function (t) { + discardable(t, testCommon, function (db, done) { + t.throws(db.get.bind(db), /^ReadError: get\(\) requires a key argument/, 'no-arg get() throws') + t.throws(db.put.bind(db), /^WriteError: put\(\) requires a key argument/, 'no-arg put() throws') + t.throws(db.del.bind(db), /^WriteError: del\(\) requires a key argument/, 'no-arg del() throws') done() }) - } - -}) + }) +} diff --git a/test/idempotent-test.js b/test/idempotent-test.js index 42648c59..2b90d424 100644 --- a/test/idempotent-test.js +++ b/test/idempotent-test.js @@ -1,42 +1,36 @@ var levelup = require('../lib/levelup.js') var memdown = require('memdown') -var common = require('./common') -var assert = require('referee').assert -var buster = require('bustermove') +var sinon = require('sinon') -buster.testCase('Idempotent open & close', { - setUp: common.readStreamSetUp, - - tearDown: common.commonTearDown, - - 'call open twice, should emit "open" once': function (done) { +module.exports = function (test, testCommon) { + test('call open twice, should emit "open" once', function (t) { var n = 0 var m = 0 var db var close = function () { - var closing = this.spy() + var closing = sinon.spy() db.on('closing', closing) db.on('closed', function () { - assert.equals(closing.callCount, 1) - assert.equals(closing.getCall(0).args, []) - done() + t.is(closing.callCount, 1) + t.same(closing.getCall(0).args, []) + t.end() }) // close needs to be idempotent too. db.close() process.nextTick(db.close.bind(db)) - }.bind(this) + } db = levelup(memdown(), function () { - assert.equals(n++, 0, 'callback should fire only once') + t.is(n++, 0, 'callback should fire only once') if (n && m) { close() } }) db.on('open', function () { - assert.equals(m++, 0, 'callback should fire only once') + t.is(m++, 0, 'callback should fire only once') if (n && m) { close() } }) db.open() - } -}) + }) +} diff --git a/test/index.js b/test/index.js index ce630379..ea0df0cf 100644 --- a/test/index.js +++ b/test/index.js @@ -3,25 +3,44 @@ if (process.browser && typeof Promise !== 'function') { global.Promise = require('pinkie') } -require('./argument-checking-test') -require('./batch-test') -require('./binary-test') -require('./clear-test') -require('./deferred-open-test') -require('./get-put-del-test') -require('./idempotent-test') -require('./init-test') -require('./inject-encoding-test') -require('./json-test') -require('./key-value-streams-test') -require('./maybe-error-test') -require('./no-encoding-test') -require('./null-and-undefined-test') -require('./open-patchsafe-test') -require('./read-stream-test') -require('./snapshot-test') -require('./iterator-test') +var test = require('tape') +var memdown = require('memdown') +var encode = require('encoding-down') +var levelup = require('../lib/levelup') + +var testCommon = require('./common')({ + test: test, + factory: function (options) { + return levelup(encode(memdown(), options)) + }, + clear: true, + deferredOpen: true, + promises: true, + streams: true, + encodings: true +}) + +require('./argument-checking-test')(test, testCommon) +require('./batch-test')(test, testCommon) +if (testCommon.encodings) require('./binary-test')(test, testCommon) +if (testCommon.clear) require('./clear-test')(test) +if (testCommon.snapshots) require('./create-stream-vs-put-racecondition')(test, testCommon) +if (testCommon.deferredOpen) require('./deferred-open-test')(test, testCommon) +require('./get-put-del-test')(test, testCommon) +require('./idempotent-test')(test, testCommon) +require('./init-test')(test, testCommon) +if (testCommon.encodings) require('./custom-encoding-test')(test, testCommon) +if (testCommon.encodings) require('./json-encoding-test')(test, testCommon) +if (testCommon.streams) require('./key-value-streams-test')(test, testCommon) +require('./maybe-error-test')(test, testCommon) +require('./no-encoding-test')(test, testCommon) +require('./null-and-undefined-test')(test, testCommon) +if (testCommon.deferredOpen) require('./open-patchsafe-test')(test, testCommon) +if (testCommon.streams) require('./read-stream-test')(test, testCommon) +if (testCommon.snapshots) require('./snapshot-test')(test, testCommon) +require('./iterator-test')(test, testCommon) +if (testCommon.seek) require('./iterator-seek-test')(test, testCommon) if (!process.browser) { - require('./browserify-test') + require('./browserify-test')(test) } diff --git a/test/init-test.js b/test/init-test.js index 6cfd38e8..e053b7a3 100644 --- a/test/init-test.js +++ b/test/init-test.js @@ -1,79 +1,75 @@ -var levelup = require('../lib/levelup.js') +var levelup = require('../lib/levelup') var memdown = require('memdown') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') -buster.testCase('Init & open()', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, +module.exports = function (test, testCommon) { + test('Init & open(): levelup()', function (t) { + t.is(typeof levelup, 'function') + t.is(levelup.length, 3) // db, options & callback arguments + t.throws(levelup, /^InitializationError/) // no db + t.end() + }) - 'levelup()': function () { - assert.isFunction(levelup) - assert.equals(levelup.length, 3) // db, options & callback arguments - assert.exception(levelup, 'InitializationError') // no db - }, - - 'open and close statuses': function (done) { + test('Init & open(): open and close statuses', function (t) { levelup(memdown(), function (err, db) { - refute(err, 'no error') - assert.isTrue(db.isOpen()) - this.closeableDatabases.push(db) + t.ifError(err, 'no error') + t.is(db.isOpen(), true) + db.close(function (err) { - refute(err) + t.ifError(err) - assert.isFalse(db.isOpen()) - assert.isTrue(db.isClosed()) + t.is(db.isOpen(), false) + t.is(db.isClosed(), true) levelup(memdown(), function (err, db) { - refute(err) - assert.isObject(db) - done() + t.ifError(err) + t.ok(typeof db === 'object' && db !== null) + + db.close(t.end.bind(t)) }) }) - }.bind(this)) - }, + }) + }) - 'without callback': function (done) { + test('Init & open(): without callback', function (t) { var db = levelup(memdown()) - this.closeableDatabases.push(db) - assert.isObject(db) + t.ok(typeof db === 'object' && db !== null) db.on('ready', function () { - assert.isTrue(db.isOpen()) - done() + t.is(db.isOpen(), true) + db.close(t.end.bind(t)) }) - }, + }) + + test('Init & open(): validate abstract-leveldown', function (t) { + t.plan(1) - 'validate abstract-leveldown': function (done) { var down = memdown() + Object.defineProperty(down, 'status', { get: function () { return null }, set: function () {} }) + try { levelup(down) } catch (err) { - assert.equals(err.message, '.status required, old abstract-leveldown') - return done() + t.is(err.message, '.status required, old abstract-leveldown') } - throw new Error('did not throw') - }, + }) - 'support open options': function (done) { + test('Init & open(): support open options', function (t) { var down = memdown() levelup(down, function (err, up) { - refute(err, 'no error') + t.ifError(err, 'no error') up.close(function () { down.open = function (opts) { - assert.equals(opts.foo, 'bar') - done() + t.is(opts.foo, 'bar') + t.end() } up.open({ foo: 'bar' }) }) }) - } -}) + }) +} diff --git a/test/inject-encoding-test.js b/test/inject-encoding-test.js deleted file mode 100644 index b323335f..00000000 --- a/test/inject-encoding-test.js +++ /dev/null @@ -1,108 +0,0 @@ -var levelup = require('../lib/levelup.js') -var memdown = require('memdown') -var encDown = require('encoding-down') -var each = require('async-each') -var parallel = require('run-parallel') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') - -buster.testCase('custom encoding', { - setUp: function (done) { - common.commonSetUp.call(this, function () { - this.runTest = function (testData, assertType, done) { - var customEncoding = { - encode: JSON.stringify, - decode: JSON.parse, - buffer: false, - type: 'custom' - } - - levelup(encDown(memdown(), { - keyEncoding: customEncoding, - valueEncoding: customEncoding - }), function (err, db) { - refute(err) - if (err) return - - this.closeableDatabases.push(db) - - var PUT = testData.map(function (d) { return db.put.bind(db, d.key, d.value) }) - parallel(PUT, function (err) { - refute(err) - each(testData, function (d, callback) { - db.get(d.key, function (err, value) { - if (err) console.error(err.stack) - refute(err) - assert[assertType](d.value, value) - callback() - }) - }, done) - }) - }.bind(this)) - } - done() - }.bind(this)) - }, - - tearDown: common.commonTearDown, - - 'simple-object values in "json" encoding': function (done) { - this.runTest([ - { key: '0', value: 0 }, - { key: '1', value: 1 }, - { key: 'string', value: 'a string' }, - { key: 'true', value: true }, - { key: 'false', value: false } - ], 'same', done) - }, - - 'simple-object keys in "json" encoding': function (done) { - this.runTest([ - // Test keys that would be considered the same with default utf8 encoding. - // Because String([1]) === String(1). - { value: '0', key: [1] }, - { value: '1', key: 1 }, - { value: 'string', key: 'a string' }, - { value: 'true', key: true }, - { value: 'false', key: false } - ], 'same', done) - }, - - 'complex-object values in "json" encoding': function (done) { - this.runTest([ - { - key: '0', - value: { - foo: 'bar', - bar: [1, 2, 3], - bang: { yes: true, no: false } - } - } - ], 'equals', done) - }, - - 'complex-object keys in "json" encoding': function (done) { - this.runTest([ - // Test keys that would be considered the same with default utf8 encoding. - // Because String({}) === String({}) === '[object Object]'. - { - value: '0', - key: { - foo: 'bar', - bar: [1, 2, 3], - bang: { yes: true, no: false } - } - }, - { - value: '1', - key: { - foo: 'different', - bar: [1, 2, 3], - bang: { yes: true, no: false } - } - } - ], 'same', done) - } -}) diff --git a/test/iterator-seek-test.js b/test/iterator-seek-test.js new file mode 100644 index 00000000..edf0dca7 --- /dev/null +++ b/test/iterator-seek-test.js @@ -0,0 +1,94 @@ +var memdown = require('memdown') +var encode = require('encoding-down') +var levelup = require('../lib/levelup') + +module.exports = function (test, testCommon) { + test('iterator#seek()', function (t) { + var mem = memdown() + + t.test('setup', function (t) { + mem.open(function (err) { + t.ifError(err, 'no open error') + mem.batch([ + { type: 'put', key: '"a"', value: 'a' }, + { type: 'put', key: '"b"', value: 'b' } + ], function (err) { + t.ifError(err, 'no batch error') + mem.close(t.end.bind(t)) + }) + }) + }) + + t.test('without encoding, without deferred-open', function (t) { + var db = levelup(mem) + + db.open(function (err) { + t.ifError(err, 'no open error') + + var it = db.iterator({ keyAsBuffer: false }) + + it.seek('"b"') + it.next(function (err, key, value) { + t.ifError(err, 'no next error') + t.is(key, '"b"') + it.end(function (err) { + t.ifError(err, 'no end error') + db.close(t.end.bind(t)) + }) + }) + }) + }) + + t.test('without encoding, with deferred-open', function (t) { + var db = levelup(mem) + var it = db.iterator({ keyAsBuffer: false }) + + it.seek('"b"') + it.next(function (err, key, value) { + t.ifError(err, 'no next error') + t.is(key, '"b"') + it.end(function (err) { + t.ifError(err, 'no end error') + db.close(t.end.bind(t)) + }) + }) + }) + + t.test('with encoding, with deferred-open', function (t) { + var db = levelup(encode(mem, { keyEncoding: 'json' })) + var it = db.iterator() + + it.seek('b') + it.next(function (err, key, value) { + t.ifError(err, 'no next error') + t.is(key, 'b') + it.end(function (err) { + t.ifError(err, 'no end error') + db.close(t.end.bind(t)) + }) + }) + }) + + t.test('with encoding, without deferred-open', function (t) { + var db = levelup(encode(mem, { keyEncoding: 'json' })) + + db.open(function (err) { + t.ifError(err, 'no open error') + + var it = db.iterator() + + it.seek('b') + it.next(function (err, key, value) { + t.ifError(err, 'no next error') + t.is(key, 'b') + it.end(function (err) { + t.ifError(err, 'no end error') + db.close(t.end.bind(t)) + }) + }) + }) + }) + + t.end() + }) +} diff --git a/test/iterator-test.js b/test/iterator-test.js index 0f5df3b7..01e50f70 100644 --- a/test/iterator-test.js +++ b/test/iterator-test.js @@ -1,21 +1,12 @@ var memdown = require('memdown') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') -var encode = require('encoding-down') var levelup = require('../lib/levelup') -var common = require('./common') -buster.testCase('iterator', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'test simple iterator': function (done) { +module.exports = function (test, testCommon) { + test('simple iterator without encoding-down', function (t) { var db = levelup(memdown()) - this.closeableDatabases.push(db) db.put('key', 'value', function (err) { - refute(err) + t.ifError(err, 'no put error') var it = db.iterator({ keyAsBuffer: false, @@ -23,86 +14,15 @@ buster.testCase('iterator', { }) it.next(function (err, key, value) { - refute(err) - - assert.equals(key, 'key') - assert.equals(value, 'value') - - it.end(done) - }) - }) - } -}) - -buster.testCase('iterator#seek()', { - setUp: function (done) { - this.mem = memdown() - this.mem.open(function () {}) - this.mem.batch([ - { type: 'put', key: '"a"', value: 'a' }, - { type: 'put', key: '"b"', value: 'b' } - ], function () {}) - this.mem.close(done) - }, - tearDown: function (done) { - this.db.close(done) - }, - - 'without encoding, without deferred-open': function (done) { - var db = this.db = levelup(this.mem) - - db.open(function (err) { - refute(err) - - var it = db.iterator({ keyAsBuffer: false }) - - it.seek('"b"') - it.next(function (err, key, value) { - refute(err) - assert.equals(key, '"b"') - it.end(done) - }) - }) - }, - - 'without encoding, with deferred-open': function (done) { - var db = this.db = levelup(this.mem) - var it = db.iterator({ keyAsBuffer: false }) - - it.seek('"b"') - it.next(function (err, key, value) { - refute(err) - assert.equals(key, '"b"') - it.end(done) - }) - }, - - 'with encoding, with deferred-open': function (done) { - var db = this.db = levelup(encode(this.mem, { keyEncoding: 'json' })) - var it = db.iterator() - - it.seek('b') - it.next(function (err, key, value) { - refute(err) - assert.equals(key, 'b') - it.end(done) - }) - }, - - 'with encoding, without deferred-open': function (done) { - var db = this.db = levelup(encode(this.mem, { keyEncoding: 'json' })) - - db.open(function (err) { - refute(err) - - var it = db.iterator() - - it.seek('b') - it.next(function (err, key, value) { - refute(err) - assert.equals(key, 'b') - it.end(done) + t.ifError(err, 'no next error') + t.is(key, 'key') + t.is(value, 'value') + + it.end(function (err) { + t.ifError(err, 'no end error') + db.close(t.end.bind(t)) + }) }) }) - } -}) + }) +} diff --git a/test/json-encoding-test.js b/test/json-encoding-test.js new file mode 100644 index 00000000..ef05e6c9 --- /dev/null +++ b/test/json-encoding-test.js @@ -0,0 +1,98 @@ +var each = require('async-each') +var parallel = require('run-parallel') +var concatStream = require('concat-stream') +var concatIterator = require('level-concat-iterator') +var discardable = require('./util/discardable') + +module.exports = function (test, testCommon) { + test('json encoding: simple-object values in "json" encoding', function (t) { + run(t, [ + { key: '0', value: 0 }, + { key: '1', value: 1 }, + { key: '2', value: 'a string' }, + { key: '3', value: true }, + { key: '4', value: false } + ]) + }) + + test('json encoding: simple-object keys in "json" encoding', function (t) { + run(t, [ + { value: 'string', key: 'a string' }, + { value: '0', key: 0 }, + { value: '1', key: 1 }, + { value: 'false', key: false }, + { value: 'true', key: true } + ]) + }) + + test('json encoding: complex-object values in "json" encoding', function (t) { + run(t, [ + { + key: '0', + value: { + foo: 'bar', + bar: [1, 2, 3], + bang: { yes: true, no: false } + } + } + ]) + }) + + test('json encoding: complex-object keys in "json" encoding', function (t) { + run(t, [ + { + value: '0', + key: { + foo: 'bar', + bar: [1, 2, 3], + bang: { yes: true, no: false } + } + } + ]) + }) + + function run (t, entries) { + discardable(t, testCommon, { + keyEncoding: 'json', + valueEncoding: 'json' + }, function (db, done) { + var ops = entries.map(function (entry) { + return { type: 'put', key: entry.key, value: entry.value } + }) + + db.batch(ops, function (err) { + t.ifError(err) + + parallel([testGet, testStream, testIterator], function (err) { + t.ifError(err) + done() + }) + }) + + function testGet (next) { + each(entries, function (entry, next) { + db.get(entry.key, function (err, value) { + t.ifError(err) + t.same(entry.value, value) + next() + }) + }, next) + } + + function testStream (next) { + db.createReadStream().pipe(concatStream(function (result) { + t.same(result, entries) + next() + })) + } + + function testIterator (next) { + concatIterator(db.iterator(), function (err, result) { + t.ifError(err) + t.same(result, entries) + next() + }) + } + }) + } +} diff --git a/test/json-test.js b/test/json-test.js deleted file mode 100644 index fc9a42f9..00000000 --- a/test/json-test.js +++ /dev/null @@ -1,101 +0,0 @@ -var levelup = require('../lib/levelup.js') -var memdown = require('memdown') -var encDown = require('encoding-down') -var each = require('async-each') -var parallel = require('run-parallel') -var concat = require('concat-stream') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') - -buster.testCase('JSON encoding', { - setUp: function (done) { - common.commonSetUp.call(this, function () { - this.runTest = function (testData, assertType, done) { - levelup(encDown(memdown(), { - keyEncoding: 'json', - valueEncoding: 'json' - }), function (err, db) { - refute(err) - if (err) return - - this.closeableDatabases.push(db) - - var PUT = testData.map(function (d) { return db.put.bind(db, d.key, d.value) }) - parallel(PUT, function (err) { - refute(err) - parallel([testGet, testStream], done) - }) - - function testGet (next) { - each(testData, function (d, callback) { - db.get(d.key, function (err, value) { - if (err) console.error(err.stack) - refute(err) - assert[assertType](d.value, value) - callback() - }) - }, next) - } - - function testStream (next) { - db.createReadStream().pipe(concat(function (result) { - assert.equals(result, testData) - next() - })) - } - }.bind(this)) - } - done() - }.bind(this)) - }, - - tearDown: common.commonTearDown, - - 'simple-object values in "json" encoding': function (done) { - this.runTest([ - { key: '0', value: 0 }, - { key: '1', value: 1 }, - { key: '2', value: 'a string' }, - { key: '3', value: true }, - { key: '4', value: false } - ], 'same', done) - }, - - 'simple-object keys in "json" encoding': function (done) { - this.runTest([ - { value: 'string', key: 'a string' }, - { value: '0', key: 0 }, - { value: '1', key: 1 }, - { value: 'false', key: false }, - { value: 'true', key: true } - ], 'same', done) - }, - - 'complex-object values in "json" encoding': function (done) { - this.runTest([ - { - key: '0', - value: { - foo: 'bar', - bar: [1, 2, 3], - bang: { yes: true, no: false } - } - } - ], 'equals', done) - }, - - 'complex-object keys in "json" encoding': function (done) { - this.runTest([ - { - value: '0', - key: { - foo: 'bar', - bar: [1, 2, 3], - bang: { yes: true, no: false } - } - } - ], 'same', done) - } -}) diff --git a/test/key-value-streams-test.js b/test/key-value-streams-test.js index 9f1fb6b7..aef0a35a 100644 --- a/test/key-value-streams-test.js +++ b/test/key-value-streams-test.js @@ -1,103 +1,114 @@ -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') -var delayed = require('delayed').delayed - -buster.testCase('Key and Value Streams', { - setUp: function (done) { - common.commonSetUp.call(this, function () { - this.dataSpy = this.spy() - this.endSpy = this.spy() - this.sourceData = [] - - for (var i = 0; i < 100; i++) { - var k = (i < 10 ? '0' : '') + i - this.sourceData.push({ - type: 'put', - key: k, - value: Math.random() - }) - } +var sinon = require('sinon') +var discardable = require('./util/discardable') + +module.exports = function (test, testCommon) { + test('key and value streams: keyStream()', function (t) { + var ctx = createContext(t) - this.sourceKeys = Object.keys(this.sourceData) - .map(function (k) { return this.sourceData[k].key }.bind(this)) - this.sourceValues = Object.keys(this.sourceData) - .map(function (k) { return this.sourceData[k].value }.bind(this)) - - this.verify = delayed(function (rs, data, done) { - assert.equals(this.endSpy.callCount, 1, 'Stream emitted single "end" event') - assert.equals(this.dataSpy.callCount, data.length, 'Stream emitted correct number of "data" events') - data.forEach(function (d, i) { - var call = this.dataSpy.getCall(i) - if (call) { - // console.log('call', i, ':', call.args[0].key, '=', call.args[0].value, '(expected', d.key, '=', d.value, ')') - assert.equals(call.args.length, 1, 'Stream "data" event #' + i + ' fired with 1 argument') - assert.equals(+call.args[0].toString(), +d, 'Stream correct "data" event #' + i + ': ' + d) - } - }.bind(this)) - done() - }, 0.05, this) - - done() - }.bind(this)) - }, - - tearDown: common.commonTearDown, - - 'test .keyStream()': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) + discardable(t, testCommon, function (db, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) var rs = db.keyStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, this.sourceKeys, done)) - }.bind(this)) - }.bind(this)) - }, - - 'test .readStream({keys:true,values:false})': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceKeys) + done() + }) + }) + }) + }) + + test('key and value streams: readStream({keys:true,values:false})', function (t) { + var ctx = createContext(t) + + discardable(t, testCommon, function (db, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) var rs = db.readStream({ keys: true, values: false }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, this.sourceKeys, done)) - }.bind(this)) - }.bind(this)) - }, - - 'test .valueStream()': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceKeys) + done() + }) + }) + }) + }) + + test('key and value streams: valueStream()', function (t) { + var ctx = createContext(t) + + discardable(t, testCommon, function (db, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) var rs = db.valueStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, this.sourceValues, done)) - }.bind(this)) - }.bind(this)) - }, - - 'test .readStream({keys:false,values:true})': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceValues) + done() + }) + }) + }) + }) + + test('key and value streams: readStream({keys:false,values:true})', function (t) { + var ctx = createContext(t) + + discardable(t, testCommon, function (db, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) var rs = db.readStream({ keys: false, values: true }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, this.sourceValues, done)) - }.bind(this)) - }.bind(this)) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceValues) + done() + }) + }) + }) + }) +} + +function createContext (t) { + var ctx = {} + + ctx.dataSpy = sinon.spy() + ctx.endSpy = sinon.spy() + ctx.sourceData = [] + + for (var i = 0; i < 100; i++) { + var k = (i < 10 ? '0' : '') + i + ctx.sourceData.push({ + type: 'put', + key: k, + value: Math.random() + }) } -}) + + ctx.sourceKeys = Object.keys(ctx.sourceData) + .map(function (k) { return ctx.sourceData[k].key }) + ctx.sourceValues = Object.keys(ctx.sourceData) + .map(function (k) { return ctx.sourceData[k].value }) + + ctx.verify = function (data) { + t.is(ctx.endSpy.callCount, 1, 'stream emitted single "end" event') + t.is(ctx.dataSpy.callCount, data.length, 'stream emitted correct number of "data" events') + + data.forEach(function (d, i) { + var call = ctx.dataSpy.getCall(i) + + if (call) { + t.is(call.args.length, 1, 'stream "data" event #' + i + ' fired with 1 argument') + t.is(+call.args[0].toString(), +d, 'stream correct "data" event #' + i + ': ' + d) + } + }) + } + + return ctx +} diff --git a/test/maybe-error-test.js b/test/maybe-error-test.js index 104509e6..b07613b5 100644 --- a/test/maybe-error-test.js +++ b/test/maybe-error-test.js @@ -1,81 +1,75 @@ 'use strict' -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') +var discardable = require('./util/discardable') -buster.testCase('maybeError() should be called async', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'put()': function (done) { - this.openTestDatabase(function (db) { +module.exports = function (test, testCommon) { + test('maybeError() should be called async: put()', function (t) { + discardable(t, testCommon, function (db, done) { db.close(function () { - assert.isTrue(db.isClosed(), 'db is closed') + t.is(db.isClosed(), true, 'db is closed') var sync = false db.put('key', 'value', {}, function (err) { sync = true - assert(err) - assert.equals(err.message, 'Database is not open') + t.ok(err) + t.is(err.message, 'Database is not open') }) - assert.isFalse(sync, '.put cb called synchronously') + t.is(sync, false, '.put cb called asynchronously') done() }) }) - }, + }) - 'get()': function (done) { - this.openTestDatabase(function (db) { + test('maybeError() should be called async: get()', function (t) { + discardable(t, testCommon, function (db, done) { db.put('key', 'value', {}, function (err) { - refute(err) + t.ifError(err) db.close(function () { - assert.isTrue(db.isClosed(), 'db is closed') + t.is(db.isClosed(), true, 'db is closed') var sync = false db.get('key', {}, function (err, value) { sync = true - assert(err) - assert.equals(err.message, 'Database is not open') + t.ok(err) + t.is(err.message, 'Database is not open') }) - assert.isFalse(sync, '.get cb called synchronously') + t.is(sync, false, '.get cb called asynchronously') done() }) }) }) - }, + }) - 'del()': function (done) { - this.openTestDatabase(function (db) { + test('maybeError() should be called async: del()', function (t) { + discardable(t, testCommon, function (db, done) { db.put('key', 'value', {}, function (err) { - refute(err) + t.ifError(err) db.close(function () { - assert.isTrue(db.isClosed(), 'db is closed') + t.is(db.isClosed(), true, 'db is closed') var sync = false db.del('key', {}, function (err) { sync = true - assert(err) - assert.equals(err.message, 'Database is not open') + t.ok(err) + t.is(err.message, 'Database is not open') }) - assert.isFalse(sync, '.del cb called synchronously') + t.is(sync, false, '.del cb called asynchronously') done() }) }) }) - }, + }) - 'batch()': function (done) { - this.openTestDatabase(function (db) { + test('maybeError() should be called async: batch()', function (t) { + discardable(t, testCommon, function (db, done) { db.close(function () { - assert.isTrue(db.isClosed(), 'db is closed') + t.is(db.isClosed(), true, 'db is closed') var sync = false db.batch([{ type: 'put', key: 'key' }], {}, function (err) { sync = true - assert(err) - assert.equals(err.message, 'Database is not open') + t.ok(err) + t.is(err.message, 'Database is not open') }) - assert.isFalse(sync, '.batch cb called synchronously') + t.is(sync, false, '.batch cb called asynchronously') done() }) }) - } -}) + }) +} diff --git a/test/no-encoding-test.js b/test/no-encoding-test.js index 88cbf3b6..fd216eb8 100644 --- a/test/no-encoding-test.js +++ b/test/no-encoding-test.js @@ -1,15 +1,8 @@ -var levelup = require('../lib/levelup.js') +var levelup = require('../lib/levelup') var memdown = require('memdown') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') -buster.testCase('without encoding-down', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'serializes key': function (done) { +module.exports = function (test, testCommon) { + test('without encoding-down: serializes key', function (t) { var down = memdown() down._serializeKey = function (key) { @@ -18,20 +11,18 @@ buster.testCase('without encoding-down', { var db = levelup(down) - this.closeableDatabases.push(db) - db.put('key', 'value', function (err) { - refute(err) + t.ifError(err) db.get('KEY', { asBuffer: false }, function (err, value) { - refute(err) - assert.same(value, 'value') - done() + t.ifError(err) + t.is(value, 'value') + db.close(t.end.bind(t)) }) }) - }, + }) - 'serializes value': function (done) { + test('without encoding-down: serializes value', function (t) { var down = memdown() down._serializeValue = function (value) { @@ -40,16 +31,14 @@ buster.testCase('without encoding-down', { var db = levelup(down) - this.closeableDatabases.push(db) - db.put('key', 'value', function (err) { - refute(err) + t.ifError(err) db.get('key', { asBuffer: false }, function (err, value) { - refute(err) - assert.same(value, 'VALUE') - done() + t.ifError(err) + t.is(value, 'VALUE') + db.close(t.end.bind(t)) }) }) - } -}) + }) +} diff --git a/test/null-and-undefined-test.js b/test/null-and-undefined-test.js index 253b8684..283b6fe4 100644 --- a/test/null-and-undefined-test.js +++ b/test/null-and-undefined-test.js @@ -1,112 +1,50 @@ -var levelup = require('../lib/levelup.js') -var memdown = require('memdown') -var errors = levelup.errors -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') - -buster.testCase('null & undefined keys & values', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'null and undefined': { - setUp: function (done) { - levelup(memdown(), function (err, db) { - refute(err) // sanity - this.closeableDatabases.push(db) - assert.isTrue(db.isOpen()) - this.db = db - done() - }.bind(this)) - }, - - 'get() with null key causes error': function (done) { - assert.exception( - this.db.get.bind(this.db, null), - { name: 'ReadError', message: 'get() requires a key argument' } - ) - done() - }, - - 'get() with undefined key causes error': function (done) { - assert.exception( - this.db.get.bind(this.db, undefined), - { name: 'ReadError', message: 'get() requires a key argument' } - ) - done() - }, - - 'del() with null key causes error': function (done) { - assert.exception( - this.db.del.bind(this.db, null), - { name: 'WriteError', message: 'del() requires a key argument' } - ) - done() - }, - - 'del() with undefined key causes error': function (done) { - assert.exception( - this.db.del.bind(this.db, undefined), - { name: 'WriteError', message: 'del() requires a key argument' } - ) - done() - }, - - 'put() with null key causes error': function (done) { - assert.exception( - this.db.put.bind(this.db, null, 'foo'), - { name: 'WriteError', message: 'put() requires a key argument' } - ) - done() - }, - - 'put() with undefined key causes error': function (done) { - assert.exception( - this.db.put.bind(this.db, undefined, 'foo'), - { name: 'WriteError', message: 'put() requires a key argument' } - ) - done() - }, - - 'put() with null value causes error': function (done) { - this.db.put('foo', null, function (err, value) { - assert.equals(err.message, 'value cannot be `null` or `undefined`') - done() +var errors = require('../lib/levelup').errors +var after = require('after') +var discardable = require('./util/discardable') + +module.exports = function (test, testCommon) { + test('null & undefined keys & values: cause error', function (t) { + discardable(t, testCommon, function (db, done) { + t.throws(db.get.bind(db, null), /^ReadError: get\(\) requires a key argument/) + t.throws(db.get.bind(db, undefined), /^ReadError: get\(\) requires a key argument/) + t.throws(db.del.bind(db, null), /^WriteError: del\(\) requires a key argument/) + t.throws(db.del.bind(db, undefined), /^WriteError: del\(\) requires a key argument/) + t.throws(db.put.bind(db, null, 'foo'), /^WriteError: put\(\) requires a key argument/) + t.throws(db.put.bind(db, undefined, 'foo'), /^WriteError: put\(\) requires a key argument/) + + var next = after(6, done) + + db.put('foo', null, function (err, value) { + t.is(err.message, 'value cannot be `null` or `undefined`') + next() }) - }, - 'put() with undefined value causes error': function (done) { - this.db.put('foo', undefined, function (err, value) { - assert.equals(err.message, 'value cannot be `null` or `undefined`') - done() + db.put('foo', undefined, function (err, value) { + t.is(err.message, 'value cannot be `null` or `undefined`') + next() }) - }, - 'batch() with undefined value causes error': function (done) { - this.db.batch([{ key: 'foo', value: undefined, type: 'put' }], function (err) { - assert.equals(err.message, 'value cannot be `null` or `undefined`') - done() + + db.batch([{ key: 'foo', value: undefined, type: 'put' }], function (err) { + t.is(err.message, 'value cannot be `null` or `undefined`') + next() }) - }, - 'batch() with null value causes error': function (done) { - this.db.batch([{ key: 'foo', value: null, type: 'put' }], function (err) { - assert.equals(err.message, 'value cannot be `null` or `undefined`') - done() + + db.batch([{ key: 'foo', value: null, type: 'put' }], function (err) { + t.is(err.message, 'value cannot be `null` or `undefined`') + next() }) - }, - 'batch() with undefined key causes error': function (done) { - this.db.batch([{ key: undefined, value: 'bar', type: 'put' }], function (err) { - assert.isInstanceOf(err, Error) - assert.isInstanceOf(err, errors.LevelUPError) - done() + + db.batch([{ key: undefined, value: 'bar', type: 'put' }], function (err) { + t.ok(err instanceof Error) + t.ok(err instanceof errors.LevelUPError) + next() }) - }, - 'batch() with null key causes error': function (done) { - this.db.batch([{ key: null, value: 'bar', type: 'put' }], function (err) { - assert.isInstanceOf(err, Error) - assert.isInstanceOf(err, errors.LevelUPError) - done() + + db.batch([{ key: null, value: 'bar', type: 'put' }], function (err) { + t.ok(err instanceof Error) + t.ok(err instanceof errors.LevelUPError) + next() }) - } - } -}) + }) + }) +} diff --git a/test/open-patchsafe-test.js b/test/open-patchsafe-test.js index dc58ebca..178c6e3a 100644 --- a/test/open-patchsafe-test.js +++ b/test/open-patchsafe-test.js @@ -1,30 +1,5 @@ -var levelup = require('../lib/levelup.js') -var memdown = require('memdown') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') - -function test (fun) { - return function (done) { - // 1) open database without callback, opens in next tick - var db = levelup(memdown()) - - this.closeableDatabases.push(db) - assert.isObject(db) - - fun(db, done) - // we should still be in a state of limbo down here, not opened or closed, but 'new' - refute(db.isOpen()) - refute(db.isClosed()) - } -} - -buster.testCase('Deferred open() is patch-safe', { - setUp: common.commonSetUp, - tearDown: common.commonTearDown, - - 'put() on pre-opened database': test(function (db, done) { +module.exports = function (test, testCommon) { + test('deferred open() is patch-safe: put() on new database', makeTest(function (t, db, done) { var put = db.put var called = 0 @@ -34,11 +9,12 @@ buster.testCase('Deferred open() is patch-safe', { } db.put('key', 'VALUE', function () { - assert.equals(called, 1) + t.is(called, 1) done() }) - }), - 'del() on pre-opened database': test(function (db, done) { + })) + + test('deferred open() is patch-safe: del() on new database', makeTest(function (t, db, done) { var del = db.del var called = 0 @@ -48,11 +24,12 @@ buster.testCase('Deferred open() is patch-safe', { } db.del('key', function () { - assert.equals(called, 1) + t.is(called, 1) done() }) - }), - 'batch() on pre-opened database': test(function (db, done) { + })) + + test('deferred open() is patch-safe: batch() on new database', makeTest(function (t, db, done) { var batch = db.batch var called = 0 @@ -65,8 +42,24 @@ buster.testCase('Deferred open() is patch-safe', { { key: 'key', value: 'v', type: 'put' }, { key: 'key2', value: 'v2', type: 'put' } ], function () { - assert.equals(called, 1) + t.is(called, 1) done() }) - }) -}) + })) + + function makeTest (fn) { + return function (t) { + // 1) open database without callback, opens in next tick + var db = testCommon.factory() + + fn(t, db, function (err) { + t.ifError(err, 'no test error') + db.close(t.end.bind(t)) + }) + + // we should still be in a state of limbo down here, not opened or closed, but 'new' + t.is(db.isOpen(), false) + t.is(db.isClosed(), false) + } + } +} diff --git a/test/read-stream-test.js b/test/read-stream-test.js index 26ea0b0c..9166246b 100644 --- a/test/read-stream-test.js +++ b/test/read-stream-test.js @@ -1,38 +1,36 @@ -var levelup = require('../lib/levelup.js') -var memdown = require('memdown') -var encDown = require('encoding-down') -var common = require('./common') -var assert = require('referee').assert -var refute = require('referee').refute -var buster = require('bustermove') +var sinon = require('sinon') var bigBlob = Array.apply(null, Array(1024 * 100)).map(function () { return 'aaaaaaaaaa' }).join('') +var discardable = require('./util/discardable') +var readStreamContext = require('./util/rs-context') + +module.exports = function (test, testCommon) { + function makeTest (fn) { + return function (t) { + discardable(t, testCommon, function (db, done) { + fn(t, db, readStreamContext(t), done) + }) + } + } -buster.testCase('ReadStream', { - setUp: common.readStreamSetUp, - - tearDown: common.commonTearDown, - - // TODO: test various encodings + test('ReadStream: simple ReadStream', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - 'test simple ReadStream': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) + var rs = db.createReadStream() + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify() + done() + }) + }) + })) - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - }.bind(this)) - }.bind(this)) - }, - - 'test pausing': function (done) { + test('ReadStream: pausing', makeTest(function (t, db, ctx, done) { var calls = 0 var rs var pauseVerify = function () { - assert.equals(calls, 5, 'stream should still be paused') + t.is(calls, 5, 'stream should still be paused') rs.resume() pauseVerify.called = true } @@ -42,554 +40,460 @@ buster.testCase('ReadStream', { setTimeout(pauseVerify, 50) } } - var verify = function () { - assert.equals(calls, this.sourceData.length, 'onData was used in test') - assert(pauseVerify.called, 'pauseVerify was used in test') - this.verify(rs, done) - }.bind(this) - - this.dataSpy = this.spy(onData) // so we can still verify - - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('end', verify.bind(this)) - }.bind(this)) - }.bind(this)) - }, - - 'test destroy() immediately': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', function () { - assert.equals(this.dataSpy.callCount, 0, '"data" event was not fired') - assert.equals(this.endSpy.callCount, 0, '"end" event was not fired') - done() - }.bind(this)) - rs.destroy() - }.bind(this)) - }.bind(this)) - }, + // so we can still verify + ctx.dataSpy = sinon.spy(onData) - 'test destroy() after close': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', function () { - rs.destroy() - done() - }) - }.bind(this)) - }.bind(this)) - }, - - 'test destroy() after closing db': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - db.close(function (err) { - refute(err) - var rs = db.createReadStream() - rs.destroy() - done() - }) + rs = db.createReadStream() + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('end', function () { + t.is(calls, ctx.sourceData.length, 'onData was used in test') + t.ok(pauseVerify.called, 'pauseVerify was used in test') + ctx.verify() + done() }) - }.bind(this)) - }, + }) + })) - 'test destroy() twice': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + test('ReadStream: destroy() immediately', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - var rs = db.createReadStream() - rs.on('data', function () { - rs.destroy() - rs.destroy() - done() - }) + var rs = db.createReadStream() + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + t.is(ctx.dataSpy.callCount, 0, '"data" event was not fired') + t.is(ctx.endSpy.callCount, 0, '"end" event was not fired') + done() }) - }.bind(this)) - }, + rs.destroy() + }) + })) - 'test destroy() half way through': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + test('ReadStream: destroy() after close', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + var rs = db.createReadStream() + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + rs.destroy() + done() + }) + }) + })) + + test('ReadStream: destroy() after closing db', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + db.close(function (err) { + t.ifError(err) var rs = db.createReadStream() - var endSpy = this.spy() - var calls = 0 - this.dataSpy = this.spy(function () { - if (++calls === 5) { rs.destroy() } - }) - rs.on('data', this.dataSpy) - rs.on('end', endSpy) - rs.on('close', function () { - // should do "data" 5 times ONLY - assert.equals(this.dataSpy.callCount, 5, 'ReadStream emitted correct number of "data" events (5)') - this.sourceData.slice(0, 5).forEach(function (d, i) { - var call = this.dataSpy.getCall(i) - assert(call) - if (call) { - assert.equals(call.args.length, 1, 'ReadStream "data" event #' + i + ' fired with 1 argument') - refute.isNull(call.args[0].key, 'ReadStream "data" event #' + i + ' argument has "key" property') - refute.isNull(call.args[0].value, 'ReadStream "data" event #' + i + ' argument has "value" property') - assert.equals(call.args[0].key, d.key, 'ReadStream "data" event #' + i + ' argument has correct "key"') - assert.equals(+call.args[0].value, +d.value, 'ReadStream "data" event #' + i + ' argument has correct "value"') - } - }.bind(this)) - done() - }.bind(this)) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "reverse=true"': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ reverse: true }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData.reverse() // for verify - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "start"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ start: '50' }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + rs.destroy() + done() + }) + }) + })) - // slice off the first 50 so verify() expects only the last 50 even though all 100 are in the db - this.sourceData = this.sourceData.slice(50) - }.bind(this)) - }.bind(this)) - }, + test('ReadStream: destroy() twice', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream() + rs.on('data', function () { + rs.destroy() + rs.destroy() + done() + }) + }) + })) - 'test readStream() with "start" and "reverse=true"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + test('ReadStream: destroy() half way through', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - var rs = db.createReadStream({ start: '50', reverse: true }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + var rs = db.createReadStream() + var endSpy = sinon.spy() + var calls = 0 + ctx.dataSpy = sinon.spy(function () { + if (++calls === 5) { rs.destroy() } + }) + rs.on('data', ctx.dataSpy) + rs.on('end', endSpy) + rs.on('close', function () { + // should do "data" 5 times ONLY + t.is(ctx.dataSpy.callCount, 5, 'ReadStream emitted correct number of "data" events (5)') + + ctx.sourceData.slice(0, 5).forEach(function (d, i) { + var call = ctx.dataSpy.getCall(i) + t.ok(call) + + if (call) { + t.is(call.args.length, 1, 'ReadStream "data" event #' + i + ' fired with 1 argument') + t.ok(call.args[0].key != null, 'ReadStream "data" event #' + i + ' argument has "key" property') + t.ok(call.args[0].value != null, 'ReadStream "data" event #' + i + ' argument has "value" property') + t.is(call.args[0].key, d.key, 'ReadStream "data" event #' + i + ' argument has correct "key"') + t.is(+call.args[0].value, +d.value, 'ReadStream "data" event #' + i + ' argument has correct "value"') + } + }) - // reverse and slice off the first 50 so verify() expects only the first 50 even though all 100 are in the db - this.sourceData.reverse() - this.sourceData = this.sourceData.slice(49) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "start" being mid-way key (float)': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - // '49.5' doesn't actually exist but we expect it to start at '50' because '49' < '49.5' < '50' (in string terms as well as numeric) - var rs = db.createReadStream({ start: '49.5' }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + done() + }) + }) + })) + + test('ReadStream: readStream() with "reverse=true"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ reverse: true }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceData.slice().reverse()) + done() + }) + }) + })) + test('ReadStream: readStream() with "start"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ start: '50' }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { // slice off the first 50 so verify() expects only the last 50 even though all 100 are in the db - this.sourceData = this.sourceData.slice(50) - }.bind(this)) - }.bind(this)) - }, + ctx.verify(ctx.sourceData.slice(50)) + done() + }) + }) + })) - 'test readStream() with "start" being mid-way key (float) and "reverse=true"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + test('ReadStream: readStream() with "start" and "reverse=true"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - var rs = db.createReadStream({ start: '49.5', reverse: true }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + var rs = db.createReadStream({ start: '50', reverse: true }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + // reverse and slice off the first 50 so verify() expects only the first 50 even though all 100 are in the db + ctx.verify(ctx.sourceData.slice().reverse().slice(49)) + done() + }) + }) + })) - // reverse & slice off the first 50 so verify() expects only the first 50 even though all 100 are in the db - this.sourceData.reverse() - this.sourceData = this.sourceData.slice(50) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "start" being mid-way key (string)': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - // '499999' doesn't actually exist but we expect it to start at '50' because '49' < '499999' < '50' (in string terms) - // the same as the previous test but we're relying solely on string ordering - var rs = db.createReadStream({ start: '499999' }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + test('ReadStream: readStream() with "start" being mid-way key (float)', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + // '49.5' doesn't actually exist but we expect it to start at '50' because '49' < '49.5' < '50' (in string terms as well as numeric) + var rs = db.createReadStream({ start: '49.5' }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { // slice off the first 50 so verify() expects only the last 50 even though all 100 are in the db - this.sourceData = this.sourceData.slice(50) - }.bind(this)) - }.bind(this)) - }, + ctx.verify(ctx.sourceData.slice(50)) + done() + }) + }) + })) - 'test readStream() with "end"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + test('ReadStream: readStream() with "start" being mid-way key (float) and "reverse=true"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - var rs = db.createReadStream({ end: '50' }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + var rs = db.createReadStream({ start: '49.5', reverse: true }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + // reverse & slice off the first 50 so verify() expects only the first 50 even though all 100 are in the db + ctx.verify(ctx.sourceData.slice().reverse().slice(50)) + done() + }) + }) + })) + + test('ReadStream: readStream() with "start" being mid-way key (string)', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + // '499999' doesn't actually exist but we expect it to start at '50' because '49' < '499999' < '50' (in string terms) + // the same as the previous test but we're relying solely on string ordering + var rs = db.createReadStream({ start: '499999' }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + // slice off the first 50 so verify() expects only the last 50 even though all 100 are in the db + ctx.verify(ctx.sourceData.slice(50)) + done() + }) + }) + })) - // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db - this.sourceData = this.sourceData.slice(0, 51) - }.bind(this)) - }.bind(this)) - }, + test('ReadStream: readStream() with "end"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - 'test readStream() with "end" being mid-way key (float)': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + var rs = db.createReadStream({ end: '50' }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db + ctx.verify(ctx.sourceData = ctx.sourceData.slice(0, 51)) + done() + }) + }) + })) - var rs = db.createReadStream({ end: '50.5' }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + test('ReadStream: readStream() with "end" being mid-way key (float)', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + var rs = db.createReadStream({ end: '50.5' }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db - this.sourceData = this.sourceData.slice(0, 51) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "end" being mid-way key (string)': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + ctx.verify(ctx.sourceData.slice(0, 51)) + done() + }) + }) + })) - var rs = db.createReadStream({ end: '50555555' }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + test('ReadStream: readStream() with "end" being mid-way key (string)', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + var rs = db.createReadStream({ end: '50555555' }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { // slice off the last 49 so verify() expects only 0 -> 50 inclusive, even though all 100 are in the db - this.sourceData = this.sourceData.slice(0, 51) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "end" being mid-way key (float) and "reverse=true"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ end: '50.5', reverse: true }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData.reverse() - this.sourceData = this.sourceData.slice(0, 49) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with both "start" and "end"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ start: 30, end: 70 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + ctx.verify(ctx.sourceData.slice(0, 51)) + done() + }) + }) + })) + + test('ReadStream: readStream() with "end" being mid-way key (float) and "reverse=true"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ end: '50.5', reverse: true }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceData.slice().reverse().slice(0, 49)) + done() + }) + }) + })) - // should include 30 to 70, inclusive - this.sourceData = this.sourceData.slice(30, 71) - }.bind(this)) - }.bind(this)) - }, + test('ReadStream: readStream() with both "start" and "end"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - 'test readStream() with both "start" and "end" and "reverse=true"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + var rs = db.createReadStream({ start: 30, end: 70 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + // should include 30 to 70, inclusive + ctx.verify(ctx.sourceData.slice(30, 71)) + done() + }) + }) + })) - var rs = db.createReadStream({ start: 70, end: 30, reverse: true }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) + test('ReadStream: readStream() with both "start" and "end" and "reverse=true"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + var rs = db.createReadStream({ start: 70, end: 30, reverse: true }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { // expect 70 -> 30 inclusive - this.sourceData.reverse() - this.sourceData = this.sourceData.slice(29, 70) - }.bind(this)) - }.bind(this)) - }, + ctx.verify(ctx.sourceData.slice().reverse().slice(29, 70)) + done() + }) + }) + })) - 'test hex encoding': function (done) { + // TODO: move this test out + testCommon.encodings && test('ReadStream: hex encoding', makeTest(function (t, db, ctx, done) { var options = { keyEncoding: 'utf8', valueEncoding: 'hex' } var data = [ { type: 'put', key: 'ab', value: 'abcdef0123456789' } ] - this.openTestDatabase({}, function (db) { - db.batch(data.slice(), options, function (err) { - refute(err) - - var rs = db.createReadStream(options) - rs.on('data', function (data) { - assert.equals(data.value, 'abcdef0123456789') - }) - rs.on('end', this.endSpy) - rs.on('close', done) - }.bind(this)) - }.bind(this)) - }, - - 'test json encoding': function (done) { - var options = { keyEncoding: 'utf8', valueEncoding: 'json' } - var data = [ - { type: 'put', key: 'aa', value: { a: 'complex', obj: 100 } }, - { type: 'put', key: 'ab', value: { b: 'foo', bar: [1, 2, 3] } }, - { type: 'put', key: 'ac', value: { c: 'w00t', d: { e: [0, 10, 20, 30], f: 1, g: 'wow' } } }, - { type: 'put', key: 'ba', value: { a: 'complex', obj: 100 } }, - { type: 'put', key: 'bb', value: { b: 'foo', bar: [1, 2, 3] } }, - { type: 'put', key: 'bc', value: { c: 'w00t', d: { e: [0, 10, 20, 30], f: 1, g: 'wow' } } }, - { type: 'put', key: 'ca', value: { a: 'complex', obj: 100 } }, - { type: 'put', key: 'cb', value: { b: 'foo', bar: [1, 2, 3] } }, - { type: 'put', key: 'cc', value: { c: 'w00t', d: { e: [0, 10, 20, 30], f: 1, g: 'wow' } } } - ] - - this.openTestDatabase(options, function (db) { - db.batch(data.slice(), function (err) { - refute(err) - - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done, data)) - }.bind(this)) - }.bind(this)) - }, - - 'test injectable encoding': function (done) { - var options = { - keyEncoding: 'utf8', - valueEncoding: { - encode: JSON.stringify, - decode: JSON.parse, - buffer: false - } - } - var data = [ - { type: 'put', key: 'aa', value: { a: 'complex', obj: 100 } }, - { type: 'put', key: 'ab', value: { b: 'foo', bar: [1, 2, 3] } }, - { type: 'put', key: 'ac', value: { c: 'w00t', d: { e: [0, 10, 20, 30], f: 1, g: 'wow' } } }, - { type: 'put', key: 'ba', value: { a: 'complex', obj: 100 } }, - { type: 'put', key: 'bb', value: { b: 'foo', bar: [1, 2, 3] } }, - { type: 'put', key: 'bc', value: { c: 'w00t', d: { e: [0, 10, 20, 30], f: 1, g: 'wow' } } }, - { type: 'put', key: 'ca', value: { a: 'complex', obj: 100 } }, - { type: 'put', key: 'cb', value: { b: 'foo', bar: [1, 2, 3] } }, - { type: 'put', key: 'cc', value: { c: 'w00t', d: { e: [0, 10, 20, 30], f: 1, g: 'wow' } } } - ] - - this.openTestDatabase(options, function (db) { - db.batch(data.slice(), function (err) { - refute(err) + db.batch(data.slice(), options, function (err) { + t.ifError(err) + var rs = db.createReadStream(options) + rs.on('data', function (data) { + t.is(data.value, 'abcdef0123456789') + }) + rs.on('end', ctx.endSpy) + rs.on('close', done) + }) + })) + + test('ReadStream: readStream() "reverse=true" not sticky (issue #6)', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + // read in reverse, assume all's good + var rs = db.createReadStream({ reverse: true }) + rs.on('close', function () { + // now try reading the other way var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done, data)) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() "reverse=true" not sticky (issue #6)': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) - // read in reverse, assume all's good - var rs = db.createReadStream({ reverse: true }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) rs.on('close', function () { - // now try reading the other way - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - }.bind(this)) - rs.resume() - }.bind(this)) - }.bind(this)) - }, - - 'test ReadStream, start=0': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ start: 0 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - }.bind(this)) - }.bind(this)) - }, + ctx.verify() + done() + }) + }) + rs.resume() + }) + })) + + test('ReadStream: ReadStream, start=0', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ start: 0 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify() + done() + }) + }) + })) // we don't expect any data to come out of here because the keys start at '00' not 0 // we just want to ensure that we don't kill the process - 'test ReadStream, end=0': function (done) { - this.openTestDatabase(function (db) { - // execute - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ end: 0 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData = [] - }.bind(this)) - }.bind(this)) - }, + test('ReadStream: ReadStream, end=0', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ end: 0 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify([]) + done() + }) + }) + })) // this is just a fancy way of testing levelup(db).createReadStream() // i.e. not waiting for 'open' to complete - // the logic for this is inside the ReadStream constructor which waits for 'ready' - 'test ReadStream on pre-opened db': function (done) { - var db = levelup(encDown(memdown())) - var execute = function () { - // is in limbo - refute(db.isOpen()) - refute(db.isClosed()) + // TODO: move this test out + testCommon.deferredOpen && test('ReadStream: deferred ReadStream on new db', function (t) { + var db = testCommon.factory() + var ctx = readStreamContext(t) + + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + db.close(function (err) { + t.ifError(err) + + var async = true + db.open(function (err) { + async = false + t.ifError(err, 'no open error') + }) - var rs = db.createReadStream() - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - }.bind(this) - var setup = function () { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - db.close(function (err) { - refute(err) - - var async = true - db.open(function (err) { - async = false - refute(err, 'no open error') - }) - - execute() - - // Should open lazily - assert(async) + // is in limbo + t.is(db.isOpen(), false) + t.is(db.isClosed(), false) + + var rs = db.createReadStream() + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify() + db.close(t.end.bind(t)) }) + + // Should open lazily + t.ok(async) + }) + }) + }) + + test('ReadStream: readStream() with "limit"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ limit: 20 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceData.slice(0, 20)) + done() + }) + }) + })) + + test('ReadStream: readStream() with "start" and "limit"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ start: '20', limit: 20 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceData.slice(20, 40)) + done() + }) + }) + })) + + test('ReadStream: readStream() with "end" after "limit"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ end: '50', limit: 20 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceData.slice(0, 20)) + done() + }) + }) + })) + + test('ReadStream: readStream() with "end" before "limit"', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) + + var rs = db.createReadStream({ end: '30', limit: 50 }) + rs.on('data', ctx.dataSpy) + rs.on('end', ctx.endSpy) + rs.on('close', function () { + ctx.verify(ctx.sourceData.slice(0, 31)) + done() }) - }.bind(this) - - setup() - }, - - 'test readStream() with "limit"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ limit: 20 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData = this.sourceData.slice(0, 20) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "start" and "limit"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ start: '20', limit: 20 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData = this.sourceData.slice(20, 40) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "end" after "limit"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ end: '50', limit: 20 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData = this.sourceData.slice(0, 20) - }.bind(this)) - }.bind(this)) - }, - - 'test readStream() with "end" before "limit"': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) - - var rs = db.createReadStream({ end: '30', limit: 50 }) - rs.on('data', this.dataSpy) - rs.on('end', this.endSpy) - rs.on('close', this.verify.bind(this, rs, done)) - - this.sourceData = this.sourceData.slice(0, 31) - }.bind(this)) - }.bind(this)) - }, + }) + })) // can, fairly reliably, trigger a core dump if next/end isn't // protected properly // the use of large blobs means that next() takes time to return // so we should be able to slip in an end() while it's working - 'test iterator next/end race condition': function (done) { + test('ReadStream: iterator next/end race condition', makeTest(function (t, db, ctx, done) { var data = [] var i = 5 var v @@ -599,29 +503,25 @@ buster.testCase('ReadStream', { data.push({ type: 'put', key: v, value: v }) } - this.openTestDatabase(function (db) { - db.batch(data, function (err) { - refute(!!err) - var rs = db.createReadStream().on('close', done) - rs.once('data', function () { - rs.destroy() - }) + db.batch(data, function (err) { + t.ifError(err) + var rs = db.createReadStream().on('close', done) + rs.once('data', function () { + rs.destroy() }) }) - }, + })) - 'test can only end once': function (done) { - this.openTestDatabase(function (db) { - db.batch(this.sourceData.slice(), function (err) { - refute(err) + test('ReadStream: can only end once', makeTest(function (t, db, ctx, done) { + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err) - var rs = db.createReadStream() - .on('close', done) + var rs = db.createReadStream() + .on('close', done) - process.nextTick(function () { - rs.destroy() - }) + process.nextTick(function () { + rs.destroy() }) - }.bind(this)) - } -}) + }) + })) +} diff --git a/test/snapshot-test.js b/test/snapshot-test.js index 16ca19da..bc1776e0 100644 --- a/test/snapshot-test.js +++ b/test/snapshot-test.js @@ -1,30 +1,29 @@ -var delayed = require('delayed') -var common = require('./common') +var delayed = require('delayed').delayed var trickle = require('trickle') -var refute = require('referee').refute -var buster = require('bustermove') +var discardable = require('./util/discardable') +var readStreamContext = require('./util/rs-context') -buster.testCase('Snapshots', { - setUp: common.readStreamSetUp, +module.exports = function (test, testCommon) { + test('ReadStream implicit snapshot', function (t) { + discardable(t, testCommon, function (db, done) { + var ctx = readStreamContext(t) - tearDown: common.commonTearDown, - - 'test ReadStream implicit snapshot': function (done) { - this.openTestDatabase(function (db) { // 1) Store 100 random numbers stored in the database - db.batch(this.sourceData.slice(), function (err) { - refute(err) + db.batch(ctx.sourceData.slice(), function (err) { + t.ifError(err, 'no batch error') // 2) Create an iterator on the current data, pipe it through a slow stream // to make *sure* that we're going to be reading it for longer than it // takes to overwrite the data in there. - var rs = db.readStream() - rs = rs.pipe(trickle({ interval: 5 })) - rs.on('data', this.dataSpy) - rs.once('end', this.endSpy) + var rs = db.readStream().pipe(trickle({ interval: 5 })) - rs.once('close', delayed.delayed(this.verify.bind(this, rs, done), 0.05)) + rs.on('data', ctx.dataSpy) + rs.once('end', ctx.endSpy) + rs.once('close', delayed(function () { + ctx.verify() + done() + }, 0.05)) process.nextTick(function () { // 3) Concoct and write new random data over the top of existing items. @@ -43,12 +42,13 @@ buster.testCase('Snapshots', { value: Math.random() }) } + db.batch(newData.slice(), function (err) { - refute(err) + t.ifError(err, 'no batch error') // we'll return here faster than it takes the readStream to complete }) }) - }.bind(this)) - }.bind(this)) - } -}) + }) + }) + }) +} diff --git a/test/util/discardable.js b/test/util/discardable.js new file mode 100644 index 00000000..53fecd06 --- /dev/null +++ b/test/util/discardable.js @@ -0,0 +1,19 @@ +module.exports = function discardable (t, testCommon, options, fn) { + if (typeof options === 'function') { + fn = options + options = {} + } + + var db = testCommon.factory(options) + + db.open(function () { + fn(db, function done (err) { + t.ifError(err, 'no test error') + + db.close(function (err) { + t.ifError(err, 'no close error') + t.end() + }) + }) + }) +} diff --git a/test/util/rs-context.js b/test/util/rs-context.js new file mode 100644 index 00000000..00869ffa --- /dev/null +++ b/test/util/rs-context.js @@ -0,0 +1,42 @@ +'use strict' + +var sinon = require('sinon') + +module.exports = function readStreamContext (t) { + var ctx = {} + var i + var k + + ctx.dataSpy = sinon.spy() + ctx.endSpy = sinon.spy() + ctx.sourceData = [] + + for (i = 0; i < 100; i++) { + k = (i < 10 ? '0' : '') + i + ctx.sourceData.push({ + type: 'put', + key: k, + value: Math.random() + }) + } + + ctx.verify = function (data) { + if (!data) data = ctx.sourceData // can pass alternative data array for verification + + t.is(ctx.endSpy.callCount, 1, 'ReadStream emitted single "end" event') + t.is(ctx.dataSpy.callCount, data.length, 'ReadStream emitted correct number of "data" events') + + data.forEach(function (d, i) { + var call = ctx.dataSpy.getCall(i) + if (call) { + t.is(call.args.length, 1, 'ReadStream "data" event #' + i + ' fired with 1 argument') + t.ok(call.args[0].key, 'ReadStream "data" event #' + i + ' argument has "key" property') + t.ok(call.args[0].value, 'ReadStream "data" event #' + i + ' argument has "value" property') + t.is(call.args[0].key, d.key, 'ReadStream "data" event #' + i + ' argument has correct "key"') + t.is(+call.args[0].value, +d.value, 'ReadStream "data" event #' + i + ' argument has correct "value"') + } + }) + } + + return ctx +}