You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
567 lines
14 KiB
567 lines
14 KiB
4 years ago
|
var test = require('tape')
|
||
|
var reachdown = require('reachdown')
|
||
|
var memdown = require('memdown')
|
||
|
var suite = require('abstract-leveldown/test')
|
||
|
var DeferredLevelDOWN = require('./')
|
||
|
var noop = function () {}
|
||
|
|
||
|
const testCommon = suite.common({
|
||
|
test: test,
|
||
|
factory: function () {
|
||
|
return new DeferredLevelDOWN(memdown())
|
||
|
},
|
||
|
|
||
|
// Unsupported features
|
||
|
createIfMissing: false,
|
||
|
errorIfExists: false,
|
||
|
|
||
|
// Opt-in to new clear() tests
|
||
|
clear: true
|
||
|
})
|
||
|
|
||
|
// Hack: disable failing tests. These fail on serialize tests
|
||
|
require('abstract-leveldown/test/put-test').args = noop
|
||
|
require('abstract-leveldown/test/get-test').args = noop
|
||
|
require('abstract-leveldown/test/del-test').args = noop
|
||
|
|
||
|
// This fails on "iterator has db reference" test, as expected because
|
||
|
// the return value of db.iterator() depends on whether the db is open.
|
||
|
require('abstract-leveldown/test/iterator-test').args = noop
|
||
|
|
||
|
// Test abstract-leveldown compliance
|
||
|
suite(testCommon)
|
||
|
|
||
|
// Custom tests
|
||
|
test('deferred open gets correct options', function (t) {
|
||
|
var OPTIONS = { foo: 'BAR' }
|
||
|
var db = {
|
||
|
open: function (options, callback) {
|
||
|
t.same(options, OPTIONS, 'options passed on to open')
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
ld.open(OPTIONS, function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('single operation', function (t) {
|
||
|
var called = false
|
||
|
var db = {
|
||
|
put: function (key, value, options, callback) {
|
||
|
t.equal(key, 'foo', 'correct key')
|
||
|
t.equal(value, 'bar', 'correct value')
|
||
|
t.deepEqual({}, options, 'empty options')
|
||
|
callback(null, 'called')
|
||
|
},
|
||
|
open: function (options, callback) {
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
ld.put('foo', 'bar', function (err, v) {
|
||
|
t.error(err, 'no error')
|
||
|
called = v
|
||
|
})
|
||
|
|
||
|
t.ok(called === false, 'not called')
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.ok(called === 'called', 'function called')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('many operations', function (t) {
|
||
|
var calls = []
|
||
|
var db = {
|
||
|
put: function (key, value, options, callback) {
|
||
|
if (puts++ === 0) {
|
||
|
t.equal(key, 'foo1', 'correct key')
|
||
|
t.equal(value, 'bar1', 'correct value')
|
||
|
t.deepEqual(options, {}, 'empty options')
|
||
|
} else {
|
||
|
t.equal(key, 'foo2', 'correct key')
|
||
|
t.equal(value, 'bar2', 'correct value')
|
||
|
t.deepEqual(options, {}, 'empty options')
|
||
|
}
|
||
|
callback(null, 'put' + puts)
|
||
|
},
|
||
|
get: function (key, options, callback) {
|
||
|
if (gets++ === 0) {
|
||
|
t.equal('woo1', key, 'correct key')
|
||
|
t.deepEqual(options, { asBuffer: true }, 'empty options')
|
||
|
} else {
|
||
|
t.equal('woo2', key, 'correct key')
|
||
|
t.deepEqual(options, { asBuffer: true }, 'empty options')
|
||
|
}
|
||
|
callback(null, 'gets' + gets)
|
||
|
},
|
||
|
del: function (key, options, callback) {
|
||
|
t.equal('blergh', key, 'correct key')
|
||
|
t.deepEqual(options, {}, 'empty options')
|
||
|
callback(null, 'del')
|
||
|
},
|
||
|
batch: function (arr, options, callback) {
|
||
|
if (batches++ === 0) {
|
||
|
t.deepEqual(arr, [
|
||
|
{ type: 'put', key: 'k1', value: 'v1' },
|
||
|
{ type: 'put', key: 'k2', value: 'v2' }
|
||
|
], 'correct batch')
|
||
|
} else {
|
||
|
t.deepEqual(arr, [
|
||
|
{ type: 'put', key: 'k3', value: 'v3' },
|
||
|
{ type: 'put', key: 'k4', value: 'v4' }
|
||
|
], 'correct batch')
|
||
|
}
|
||
|
callback()
|
||
|
},
|
||
|
clear: function (options, callback) {
|
||
|
if (clears++ === 0) {
|
||
|
t.deepEqual(options, { reverse: false, limit: -1 }, 'default options')
|
||
|
} else {
|
||
|
t.deepEqual(options, { gt: 'k5', reverse: false, limit: -1 }, 'range option')
|
||
|
}
|
||
|
|
||
|
callback()
|
||
|
},
|
||
|
open: function (options, callback) {
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
var puts = 0
|
||
|
var gets = 0
|
||
|
var batches = 0
|
||
|
var clears = 0
|
||
|
|
||
|
ld.put('foo1', 'bar1', function (err, v) {
|
||
|
t.error(err, 'no error')
|
||
|
calls.push({ type: 'put', key: 'foo1', v: v })
|
||
|
})
|
||
|
ld.get('woo1', function (err, v) {
|
||
|
t.error(err, 'no error')
|
||
|
calls.push({ type: 'get', key: 'woo1', v: v })
|
||
|
})
|
||
|
ld.clear(function () {
|
||
|
calls.push({ type: 'clear' })
|
||
|
})
|
||
|
ld.put('foo2', 'bar2', function (err, v) {
|
||
|
t.error(err, 'no error')
|
||
|
calls.push({ type: 'put', key: 'foo2', v: v })
|
||
|
})
|
||
|
ld.get('woo2', function (err, v) {
|
||
|
t.error(err, 'no error')
|
||
|
calls.push({ type: 'get', key: 'woo2', v: v })
|
||
|
})
|
||
|
ld.del('blergh', function (err, v) {
|
||
|
t.error(err, 'no error')
|
||
|
calls.push({ type: 'del', key: 'blergh', v: v })
|
||
|
})
|
||
|
ld.batch([
|
||
|
{ type: 'put', key: 'k1', value: 'v1' },
|
||
|
{ type: 'put', key: 'k2', value: 'v2' }
|
||
|
], function () {
|
||
|
calls.push({ type: 'batch', keys: 'k1,k2' })
|
||
|
})
|
||
|
ld
|
||
|
.batch()
|
||
|
.put('k3', 'v3')
|
||
|
.put('k4', 'v4')
|
||
|
.write(function () {
|
||
|
calls.push({ type: 'batch', keys: 'k3,k4' })
|
||
|
})
|
||
|
ld.clear({ gt: 'k5' }, function () {
|
||
|
calls.push({ type: 'clear', gt: 'k5' })
|
||
|
})
|
||
|
|
||
|
t.ok(calls.length === 0, 'not called')
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
|
||
|
t.equal(calls.length, 9, 'all functions called')
|
||
|
t.deepEqual(calls, [
|
||
|
{ type: 'put', key: 'foo1', v: 'put1' },
|
||
|
{ type: 'get', key: 'woo1', v: 'gets1' },
|
||
|
{ type: 'clear' },
|
||
|
{ type: 'put', key: 'foo2', v: 'put2' },
|
||
|
{ type: 'get', key: 'woo2', v: 'gets2' },
|
||
|
{ type: 'del', key: 'blergh', v: 'del' },
|
||
|
{ type: 'batch', keys: 'k1,k2' },
|
||
|
{ type: 'batch', keys: 'k3,k4' },
|
||
|
{ type: 'clear', gt: 'k5' }
|
||
|
], 'calls correctly behaved')
|
||
|
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('keys and values should not be serialized', function (t) {
|
||
|
var DATA = []
|
||
|
var ITEMS = [
|
||
|
123,
|
||
|
'a string',
|
||
|
Buffer.from('w00t'),
|
||
|
{ an: 'object' }
|
||
|
]
|
||
|
ITEMS.forEach(function (k) {
|
||
|
ITEMS.forEach(function (v) {
|
||
|
DATA.push({ key: k, value: v })
|
||
|
})
|
||
|
})
|
||
|
|
||
|
function Db (m, fn) {
|
||
|
var db = {
|
||
|
open: function (options, cb) {
|
||
|
process.nextTick(cb)
|
||
|
}
|
||
|
}
|
||
|
var wrapper = function () {
|
||
|
fn.apply(null, arguments)
|
||
|
}
|
||
|
db[m] = wrapper
|
||
|
return new DeferredLevelDOWN(db)
|
||
|
}
|
||
|
|
||
|
function noop () {}
|
||
|
|
||
|
t.plan(8)
|
||
|
|
||
|
t.test('put', function (t) {
|
||
|
var calls = []
|
||
|
var ld = Db('put', function (key, value, cb) {
|
||
|
calls.push({ key: key, value: value })
|
||
|
})
|
||
|
DATA.forEach(function (d) { ld.put(d.key, d.value, noop) })
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(calls, DATA, 'value ok')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.test('get', function (t) {
|
||
|
var calls = []
|
||
|
var ld = Db('get', function (key, cb) { calls.push(key) })
|
||
|
ITEMS.forEach(function (key) { ld.get(key, noop) })
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(calls, ITEMS, 'value ok')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.test('del', function (t) {
|
||
|
var calls = []
|
||
|
var ld = Db('del', function (key, cb) { calls.push(key) })
|
||
|
ITEMS.forEach(function (key) { ld.del(key, noop) })
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(calls, ITEMS, 'value ok')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.test('clear', function (t) {
|
||
|
var calls = []
|
||
|
var ld = Db('clear', function (opts, cb) { calls.push(opts) })
|
||
|
ITEMS.forEach(function (key) { ld.clear({ gt: key }, noop) })
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(calls, ITEMS.map(function (key) {
|
||
|
return { gt: key, reverse: false, limit: -1 }
|
||
|
}), 'value ok')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.test('approximateSize', function (t) {
|
||
|
var calls = []
|
||
|
var ld = Db('approximateSize', function (start, end, cb) {
|
||
|
calls.push({ start: start, end: end })
|
||
|
})
|
||
|
ITEMS.forEach(function (key) { ld.approximateSize(key, key, noop) })
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(calls, ITEMS.map(function (i) {
|
||
|
return { start: i, end: i }
|
||
|
}), 'value ok')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.test('store not supporting approximateSize', function (t) {
|
||
|
var ld = Db('FOO', function () {})
|
||
|
t.throws(function () {
|
||
|
ld.approximateSize('key', 'key', noop)
|
||
|
}, /approximateSize is not a function/)
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
t.test('compactRange', function (t) {
|
||
|
var calls = []
|
||
|
var ld = Db('compactRange', function (start, end, cb) {
|
||
|
calls.push({ start: start, end: end })
|
||
|
})
|
||
|
ITEMS.forEach(function (key) { ld.compactRange(key, key, noop) })
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(calls, ITEMS.map(function (i) {
|
||
|
return { start: i, end: i }
|
||
|
}), 'value ok')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
t.test('store not supporting compactRange', function (t) {
|
||
|
var ld = Db('FOO', function () {})
|
||
|
t.throws(function () {
|
||
|
ld.compactRange('key', 'key', noop)
|
||
|
}, /compactRange is not a function/)
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('_close calls close for underlying store', function (t) {
|
||
|
t.plan(2)
|
||
|
|
||
|
var db = {
|
||
|
close: function (callback) {
|
||
|
t.pass('close for underlying store is called')
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
|
||
|
ld.close(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('open error on underlying store calls back with error', function (t) {
|
||
|
t.plan(2)
|
||
|
|
||
|
var db = {
|
||
|
open: function (options, callback) {
|
||
|
t.pass('db.open called')
|
||
|
process.nextTick(callback, new Error('foo'))
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.is(err.message, 'foo')
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('close error on underlying store calls back with error', function (t) {
|
||
|
t.plan(2)
|
||
|
|
||
|
var db = {
|
||
|
close: function (callback) {
|
||
|
t.pass('db.close called')
|
||
|
process.nextTick(callback, new Error('foo'))
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
|
||
|
ld.close(function (err) {
|
||
|
t.is(err.message, 'foo')
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('non-deferred approximateSize', function (t) {
|
||
|
t.plan(4)
|
||
|
|
||
|
var db = {
|
||
|
open: function (options, cb) {
|
||
|
process.nextTick(cb)
|
||
|
},
|
||
|
approximateSize: function (start, end, callback) {
|
||
|
t.is(start, 'bar')
|
||
|
t.is(end, 'foo')
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.error(err)
|
||
|
ld.approximateSize('bar', 'foo', function (err) {
|
||
|
t.error(err)
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('non-deferred compactRange', function (t) {
|
||
|
t.plan(4)
|
||
|
|
||
|
var db = {
|
||
|
open: function (options, cb) {
|
||
|
process.nextTick(cb)
|
||
|
},
|
||
|
compactRange: function (start, end, callback) {
|
||
|
t.is(start, 'bar')
|
||
|
t.is(end, 'foo')
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.error(err)
|
||
|
ld.compactRange('bar', 'foo', function (err) {
|
||
|
t.error(err)
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('iterator - deferred operations', function (t) {
|
||
|
t.plan(9)
|
||
|
|
||
|
var seekTarget = false
|
||
|
|
||
|
var db = {
|
||
|
iterator: function (options) {
|
||
|
return {
|
||
|
seek: function (target) {
|
||
|
seekTarget = target
|
||
|
},
|
||
|
next: function (cb) {
|
||
|
cb(null, 'key', 'value')
|
||
|
},
|
||
|
end: function (cb) {
|
||
|
process.nextTick(cb)
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
open: function (options, callback) {
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
var it = ld.iterator()
|
||
|
var nextFirst = false
|
||
|
|
||
|
it.seek('foo')
|
||
|
|
||
|
it.next(function (err, key, value) {
|
||
|
t.is(seekTarget, 'foo', 'seek was called with correct target')
|
||
|
nextFirst = true
|
||
|
t.error(err, 'no error')
|
||
|
t.equal(key, 'key')
|
||
|
t.equal(value, 'value')
|
||
|
})
|
||
|
|
||
|
it.end(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.ok(nextFirst)
|
||
|
})
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
var it2 = ld.iterator()
|
||
|
it2.end(t.error.bind(t))
|
||
|
})
|
||
|
|
||
|
t.ok(require('./').DeferredIterator)
|
||
|
})
|
||
|
|
||
|
test('iterator - non deferred operation', function (t) {
|
||
|
t.plan(5)
|
||
|
var seekTarget = false
|
||
|
|
||
|
var db = {
|
||
|
iterator: function (options) {
|
||
|
return {
|
||
|
next: function (cb) {
|
||
|
cb(null, 'key', 'value')
|
||
|
},
|
||
|
seek: function (target) {
|
||
|
seekTarget = target
|
||
|
},
|
||
|
end: function (cb) {
|
||
|
process.nextTick(cb)
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
open: function (options, callback) {
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
var it = ld.iterator()
|
||
|
|
||
|
ld.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
|
||
|
it.seek('foo')
|
||
|
|
||
|
t.is(seekTarget, 'foo', 'seek was called with correct target')
|
||
|
|
||
|
it.next(function (err, key, value) {
|
||
|
t.error(err, 'no error')
|
||
|
t.equal(key, 'key')
|
||
|
t.equal(value, 'value')
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('iterator - is created in order', function (t) {
|
||
|
t.plan(4)
|
||
|
|
||
|
function db () {
|
||
|
return {
|
||
|
order: [],
|
||
|
iterator: function (options) {
|
||
|
this.order.push('iterator created')
|
||
|
return {}
|
||
|
},
|
||
|
put: function (key, value, options, callback) {
|
||
|
this.order.push('put')
|
||
|
},
|
||
|
open: function (options, callback) {
|
||
|
process.nextTick(callback)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var ld1 = new DeferredLevelDOWN(db())
|
||
|
var ld2 = new DeferredLevelDOWN(db())
|
||
|
|
||
|
ld1.iterator()
|
||
|
ld1.put('key', 'value', noop)
|
||
|
|
||
|
ld2.put('key', 'value', noop)
|
||
|
ld2.iterator()
|
||
|
|
||
|
ld1.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(ld1._db.order, ['iterator created', 'put'])
|
||
|
})
|
||
|
|
||
|
ld2.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.same(ld2._db.order, ['put', 'iterator created'])
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('reachdown supports deferred-leveldown', function (t) {
|
||
|
// Define just enough methods for reachdown to see this as a real db
|
||
|
var db = { open: noop, _batch: noop, _iterator: noop }
|
||
|
var ld = new DeferredLevelDOWN(db)
|
||
|
|
||
|
t.is(ld.type, 'deferred-leveldown')
|
||
|
t.is(reachdown(ld, 'deferred-leveldown'), ld)
|
||
|
t.is(reachdown(ld), db)
|
||
|
|
||
|
t.end()
|
||
|
})
|