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.
372 lines
8.8 KiB
372 lines
8.8 KiB
4 years ago
|
var test = require('tape')
|
||
|
var leveldown = require('leveldown')
|
||
|
var iteratorStream = require('./')
|
||
|
var through2 = require('through2')
|
||
|
var tempy = require('tempy')
|
||
|
var addSecretListener = require('secret-event-listener')
|
||
|
|
||
|
var db
|
||
|
var data = [
|
||
|
{ type: 'put', key: 'foobatch1', value: 'bar1' },
|
||
|
{ type: 'put', key: 'foobatch2', value: 'bar2' },
|
||
|
{ type: 'put', key: 'foobatch3', value: 'bar3' }
|
||
|
]
|
||
|
|
||
|
test('setup', function (t) {
|
||
|
db = leveldown(tempy.directory())
|
||
|
db.open(function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
db.batch(data, function (err) {
|
||
|
t.error(err, 'no error')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('keys and values', function (t) {
|
||
|
var idx = 0
|
||
|
var stream = iteratorStream(db.iterator())
|
||
|
stream.pipe(through2.obj(function (kv, _, done) {
|
||
|
t.ok(Buffer.isBuffer(kv.key))
|
||
|
t.ok(Buffer.isBuffer(kv.value))
|
||
|
t.equal(kv.key.toString(), data[idx].key)
|
||
|
t.equal(kv.value.toString(), data[idx].value)
|
||
|
idx++
|
||
|
done()
|
||
|
}, function () {
|
||
|
t.equal(idx, data.length)
|
||
|
stream.on('close', function () {
|
||
|
t.end()
|
||
|
})
|
||
|
}))
|
||
|
})
|
||
|
|
||
|
test('normal event order', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order.filter(withoutDataEvents), ['_end', 'end', 'close'])
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('error from iterator.next', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'error: next', 'close'], 'event order')
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
iterator.next = function (cb) {
|
||
|
process.nextTick(cb, new Error('next'))
|
||
|
}
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('error from iterator end', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
var _end = iterator._end
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order.filter(withoutDataEvents), ['_end', 'end', 'error: end', 'close'])
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
iterator._end = function (cb) {
|
||
|
order.push('_end')
|
||
|
_end.call(this, function (err) {
|
||
|
t.ifError(err)
|
||
|
cb(new Error('end'))
|
||
|
})
|
||
|
}
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('.destroy', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'close'])
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
stream.destroy()
|
||
|
})
|
||
|
|
||
|
test('.destroy(err)', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'error: user', 'close'])
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
stream.destroy(new Error('user'))
|
||
|
})
|
||
|
|
||
|
test('.destroy(err, callback)', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'callback', 'close'])
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
stream.destroy(new Error('user'), function (err) {
|
||
|
order.push('callback')
|
||
|
t.is(err.message, 'user', 'got error')
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('.destroy(null, callback)', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'callback', 'close'])
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
stream.destroy(null, function (err) {
|
||
|
order.push('callback')
|
||
|
t.ifError(err, 'no error')
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('.destroy() during iterator.next', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'close'], 'event order')
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
iterator.next = function () {
|
||
|
stream.destroy()
|
||
|
}
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('.destroy(err) during iterator.next', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'error: user', 'close'], 'event order')
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
iterator.next = function (cb) {
|
||
|
stream.destroy(new Error('user'))
|
||
|
}
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('.destroy(err, callback) during iterator.next', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'callback', 'close'], 'event order')
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
iterator.next = function (cb) {
|
||
|
stream.destroy(new Error('user'), function (err) {
|
||
|
order.push('callback')
|
||
|
t.is(err.message, 'user', 'got error')
|
||
|
})
|
||
|
}
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('.destroy(null, callback) during iterator.next', function (t) {
|
||
|
var iterator = db.iterator()
|
||
|
var stream = iteratorStream(iterator)
|
||
|
|
||
|
var order = monitor(iterator, stream, function () {
|
||
|
t.same(order, ['_end', 'callback', 'close'], 'event order')
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
iterator.next = function (cb) {
|
||
|
stream.destroy(null, function (err) {
|
||
|
order.push('callback')
|
||
|
t.ifError(err, 'no error')
|
||
|
})
|
||
|
}
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
test('.destroy during iterator.next 1', function (t) {
|
||
|
var stream
|
||
|
var iterator = db.iterator()
|
||
|
var next = iterator.next.bind(iterator)
|
||
|
iterator.next = function (cb) {
|
||
|
t.pass('should be called once')
|
||
|
next(cb)
|
||
|
stream.destroy()
|
||
|
}
|
||
|
stream = iteratorStream(iterator)
|
||
|
stream.on('data', function (data) {
|
||
|
t.fail('should not be called')
|
||
|
})
|
||
|
stream.on('close', t.end.bind(t))
|
||
|
})
|
||
|
|
||
|
test('.destroy during iterator.next 2', function (t) {
|
||
|
var stream
|
||
|
var iterator = db.iterator()
|
||
|
var next = iterator.next.bind(iterator)
|
||
|
var count = 0
|
||
|
iterator.next = function (cb) {
|
||
|
t.pass('should be called')
|
||
|
next(cb)
|
||
|
if (++count === 2) {
|
||
|
stream.destroy()
|
||
|
}
|
||
|
}
|
||
|
stream = iteratorStream(iterator)
|
||
|
stream.on('data', function (data) {
|
||
|
t.pass('should be called')
|
||
|
})
|
||
|
stream.on('close', t.end.bind(t))
|
||
|
})
|
||
|
|
||
|
test('.destroy after iterator.next 1', function (t) {
|
||
|
var stream
|
||
|
var iterator = db.iterator()
|
||
|
var next = iterator.next.bind(iterator)
|
||
|
iterator.next = function (cb) {
|
||
|
next(function (err, key, value) {
|
||
|
stream.destroy()
|
||
|
cb(err, key, value)
|
||
|
t.pass('should be called')
|
||
|
})
|
||
|
}
|
||
|
stream = iteratorStream(iterator)
|
||
|
stream.on('data', function (data) {
|
||
|
t.fail('should not be called')
|
||
|
})
|
||
|
stream.on('close', t.end.bind(t))
|
||
|
})
|
||
|
|
||
|
test('.destroy after iterator.next 2', function (t) {
|
||
|
var stream
|
||
|
var iterator = db.iterator()
|
||
|
var next = iterator.next.bind(iterator)
|
||
|
var count = 0
|
||
|
iterator.next = function (cb) {
|
||
|
next(function (err, key, value) {
|
||
|
if (++count === 2) {
|
||
|
stream.destroy()
|
||
|
}
|
||
|
cb(err, key, value)
|
||
|
t.pass('should be called')
|
||
|
})
|
||
|
}
|
||
|
stream = iteratorStream(iterator)
|
||
|
stream.on('data', function (data) {
|
||
|
t.pass('should be called')
|
||
|
})
|
||
|
stream.on('close', t.end.bind(t))
|
||
|
})
|
||
|
|
||
|
test('keys=false', function (t) {
|
||
|
var stream = iteratorStream(db.iterator(), { keys: false })
|
||
|
stream.once('data', function (value) {
|
||
|
stream.destroy()
|
||
|
t.equal(value.toString(), 'bar1')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
test('values=false', function (t) {
|
||
|
var stream = iteratorStream(db.iterator(), { values: false })
|
||
|
stream.once('data', function (key) {
|
||
|
stream.destroy()
|
||
|
t.equal(key.toString(), 'foobatch1')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
// It's important to keep a reference to the iterator at least until we end,
|
||
|
// to prevent GC of the iterator and therefor its db (esp. for native addons).
|
||
|
test('keeps a reference to the iterator', function (t) {
|
||
|
var it = db.iterator()
|
||
|
var stream = iteratorStream(it)
|
||
|
|
||
|
stream.on('close', function () {
|
||
|
t.is(stream._iterator, it, 'has reference')
|
||
|
t.end()
|
||
|
})
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
// Note: also serves as teardown of above tests
|
||
|
test('it is safe to close db on end of stream', function (t) {
|
||
|
// Set highWaterMark to 0 so that we don't preemptively fetch.
|
||
|
var it = db.iterator({ highWaterMark: 0 })
|
||
|
var stream = iteratorStream(it)
|
||
|
|
||
|
stream.on('end', function () {
|
||
|
// Although the underlying iterator is still alive at this point (before
|
||
|
// the 'close' event has been emitted) it's safe to close the db because
|
||
|
// leveldown (v5) ends any open iterators before closing.
|
||
|
db.close(function (err) {
|
||
|
t.ifError(err, 'no error')
|
||
|
t.end()
|
||
|
})
|
||
|
})
|
||
|
|
||
|
stream.resume()
|
||
|
})
|
||
|
|
||
|
function monitor (iterator, stream, onClose) {
|
||
|
var order = []
|
||
|
|
||
|
;['_next', '_end'].forEach(function (method) {
|
||
|
var original = iterator[method]
|
||
|
|
||
|
iterator[method] = function () {
|
||
|
order.push(method)
|
||
|
original.apply(this, arguments)
|
||
|
}
|
||
|
})
|
||
|
|
||
|
;['data', 'end', 'error', 'close'].forEach(function (event) {
|
||
|
// Add listener without side effects (like triggering flowing mode)
|
||
|
addSecretListener(stream, event, function (err) {
|
||
|
if (event === 'error') order.push('error: ' + err.message)
|
||
|
else order.push(event)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
if (onClose) {
|
||
|
addSecretListener(stream, 'close', onClose)
|
||
|
}
|
||
|
|
||
|
return order
|
||
|
}
|
||
|
|
||
|
function withoutDataEvents (event) {
|
||
|
return event !== '_next' && event !== 'data'
|
||
|
}
|