299 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			299 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict'
 | |
| 
 | |
| const test = require('tape')
 | |
| const { DeferredIterator, DeferredKeyIterator, DeferredValueIterator } = require('../../lib/deferred-iterator')
 | |
| const { AbstractIterator, AbstractKeyIterator, AbstractValueIterator } = require('../..')
 | |
| const { mockLevel } = require('../util')
 | |
| const noop = () => {}
 | |
| const identity = (v) => v
 | |
| 
 | |
| for (const mode of ['iterator', 'keys', 'values']) {
 | |
|   const RealCtor = mode === 'iterator' ? AbstractIterator : mode === 'keys' ? AbstractKeyIterator : AbstractValueIterator
 | |
|   const DeferredCtor = mode === 'iterator' ? DeferredIterator : mode === 'keys' ? DeferredKeyIterator : DeferredValueIterator
 | |
|   const nextArgs = mode === 'iterator' ? ['key', 'value'] : mode === 'keys' ? ['key'] : ['value']
 | |
|   const privateMethod = '_' + mode
 | |
|   const publicMethod = mode
 | |
| 
 | |
|   // NOTE: adapted from deferred-leveldown
 | |
|   test(`deferred ${mode}()`, function (t) {
 | |
|     t.plan(8)
 | |
| 
 | |
|     const keyEncoding = {
 | |
|       format: 'utf8',
 | |
|       encode (key) {
 | |
|         t.is(key, 'foo', 'encoding got key')
 | |
|         return key.toUpperCase()
 | |
|       },
 | |
|       decode: identity
 | |
|     }
 | |
| 
 | |
|     class MockIterator extends RealCtor {
 | |
|       _next (cb) {
 | |
|         this.nextTick(cb, null, ...nextArgs)
 | |
|       }
 | |
| 
 | |
|       _close (cb) {
 | |
|         this.nextTick(cb)
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const db = mockLevel({
 | |
|       [privateMethod]: function (options) {
 | |
|         t.is(options.gt, 'FOO', 'got encoded range option')
 | |
|         return new MockIterator(this, options)
 | |
|       },
 | |
|       _open: function (options, callback) {
 | |
|         t.pass('opened')
 | |
|         this.nextTick(callback)
 | |
|       }
 | |
|     }, { encodings: { utf8: true } }, {
 | |
|       keyEncoding
 | |
|     })
 | |
| 
 | |
|     const it = db[publicMethod]({ gt: 'foo' })
 | |
|     t.ok(it instanceof DeferredCtor, 'is deferred')
 | |
| 
 | |
|     let nextFirst = false
 | |
| 
 | |
|     it.next(function (err, ...rest) {
 | |
|       nextFirst = true
 | |
|       t.error(err, 'no next() error')
 | |
|       t.same(rest, nextArgs)
 | |
|     })
 | |
| 
 | |
|     it.close(function (err) {
 | |
|       t.error(err, 'no close() error')
 | |
|       t.ok(nextFirst)
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   // NOTE: adapted from deferred-leveldown
 | |
|   test(`deferred ${mode}(): non-deferred operations`, function (t) {
 | |
|     t.plan(6)
 | |
| 
 | |
|     class MockIterator extends RealCtor {
 | |
|       _seek (target) {
 | |
|         t.is(target, '123')
 | |
|       }
 | |
| 
 | |
|       _next (cb) {
 | |
|         this.nextTick(cb, null, ...nextArgs)
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const db = mockLevel({
 | |
|       [privateMethod]: function (options) {
 | |
|         return new MockIterator(this, options)
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     db.open(function (err) {
 | |
|       t.error(err, 'no open() error')
 | |
| 
 | |
|       it.seek(123)
 | |
|       it.next(function (err, ...rest) {
 | |
|         t.error(err, 'no next() error')
 | |
|         t.same(rest, nextArgs)
 | |
| 
 | |
|         it.close(function (err) {
 | |
|           t.error(err, 'no close() error')
 | |
|         })
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     const it = db[publicMethod]({ gt: 'foo' })
 | |
|     t.ok(it instanceof DeferredCtor)
 | |
|   })
 | |
| 
 | |
|   // NOTE: adapted from deferred-leveldown
 | |
|   test(`deferred ${mode}(): iterators are created in order`, function (t) {
 | |
|     t.plan(6)
 | |
| 
 | |
|     const order1 = []
 | |
|     const order2 = []
 | |
| 
 | |
|     class MockIterator extends RealCtor {}
 | |
| 
 | |
|     function db (order) {
 | |
|       return mockLevel({
 | |
|         [privateMethod]: function (options) {
 | |
|           order.push('iterator created')
 | |
|           return new MockIterator(this, options)
 | |
|         },
 | |
|         _put: function (key, value, options, callback) {
 | |
|           order.push('put')
 | |
|         },
 | |
|         _open: function (options, callback) {
 | |
|           this.nextTick(callback)
 | |
|         }
 | |
|       })
 | |
|     }
 | |
| 
 | |
|     const db1 = db(order1)
 | |
|     const db2 = db(order2)
 | |
| 
 | |
|     db1.open(function (err) {
 | |
|       t.error(err, 'no error')
 | |
|       t.same(order1, ['iterator created', 'put'])
 | |
|     })
 | |
| 
 | |
|     db2.open(function (err) {
 | |
|       t.error(err, 'no error')
 | |
|       t.same(order2, ['put', 'iterator created'])
 | |
|     })
 | |
| 
 | |
|     t.ok(db1[publicMethod]() instanceof DeferredCtor)
 | |
|     db1.put('key', 'value', noop)
 | |
| 
 | |
|     db2.put('key', 'value', noop)
 | |
|     t.ok(db2[publicMethod]() instanceof DeferredCtor)
 | |
|   })
 | |
| 
 | |
|   for (const method of ['next', 'nextv', 'all']) {
 | |
|     test(`deferred ${mode}(): closed upon failed open, verified by ${method}()`, function (t) {
 | |
|       t.plan(5)
 | |
| 
 | |
|       const db = mockLevel({
 | |
|         _open (options, callback) {
 | |
|           t.pass('opening')
 | |
|           this.nextTick(callback, new Error('_open error'))
 | |
|         },
 | |
|         _iterator () {
 | |
|           t.fail('should not be called')
 | |
|         },
 | |
|         [privateMethod] () {
 | |
|           t.fail('should not be called')
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       const it = db[publicMethod]()
 | |
|       t.ok(it instanceof DeferredCtor)
 | |
| 
 | |
|       const original = it._close
 | |
|       it._close = function (...args) {
 | |
|         t.pass('closed')
 | |
|         return original.call(this, ...args)
 | |
|       }
 | |
| 
 | |
|       verifyClosed(t, it, method, () => {})
 | |
|     })
 | |
| 
 | |
|     test(`deferred ${mode}(): deferred and real iterators are closed on db.close(), verified by ${method}()`, function (t) {
 | |
|       t.plan(10)
 | |
| 
 | |
|       class MockIterator extends RealCtor {
 | |
|         _close (callback) {
 | |
|           t.pass('closed')
 | |
|           this.nextTick(callback)
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       const db = mockLevel({
 | |
|         [privateMethod] (options) {
 | |
|           return new MockIterator(this, options)
 | |
|         }
 | |
|       })
 | |
| 
 | |
|       const it = db[publicMethod]()
 | |
|       t.ok(it instanceof DeferredCtor)
 | |
| 
 | |
|       const original = it._close
 | |
|       it._close = function (...args) {
 | |
|         t.pass('closed')
 | |
|         return original.call(this, ...args)
 | |
|       }
 | |
| 
 | |
|       db.close(function (err) {
 | |
|         t.ifError(err, 'no close() error')
 | |
| 
 | |
|         verifyClosed(t, it, method, function () {
 | |
|           db.open(function (err) {
 | |
|             t.ifError(err, 'no open() error')
 | |
| 
 | |
|             // Should still be closed
 | |
|             verifyClosed(t, it, method, function () {
 | |
|               db.close(t.ifError.bind(t))
 | |
|             })
 | |
|           })
 | |
|         })
 | |
|       })
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   test(`deferred ${mode}(): deferred and real iterators are detached on db.close()`, function (t) {
 | |
|     t.plan(4)
 | |
| 
 | |
|     class MockIterator extends RealCtor {}
 | |
| 
 | |
|     let real
 | |
|     const db = mockLevel({
 | |
|       [privateMethod] (options) {
 | |
|         real = new MockIterator(this, options)
 | |
|         return real
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     const it = db[publicMethod]()
 | |
|     t.ok(it instanceof DeferredCtor)
 | |
| 
 | |
|     db.close(function (err) {
 | |
|       t.ifError(err, 'no close() error')
 | |
| 
 | |
|       db.open(function (err) {
 | |
|         t.ifError(err, 'no open() error')
 | |
| 
 | |
|         it.close = real.close = it._close = real._close = function () {
 | |
|           t.fail('should not be called')
 | |
|         }
 | |
| 
 | |
|         db.close(t.ifError.bind(t))
 | |
|       })
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   test(`deferred ${mode}(): defers underlying close()`, function (t) {
 | |
|     t.plan(3)
 | |
| 
 | |
|     class MockIterator extends RealCtor {
 | |
|       _close (callback) {
 | |
|         order.push('_close')
 | |
|         this.nextTick(callback)
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     const order = []
 | |
|     const db = mockLevel({
 | |
|       _open (options, callback) {
 | |
|         order.push('_open')
 | |
|         this.nextTick(callback)
 | |
|       },
 | |
|       [privateMethod] (options) {
 | |
|         order.push(privateMethod)
 | |
|         return new MockIterator(this, options)
 | |
|       }
 | |
|     })
 | |
| 
 | |
|     const it = db[publicMethod]()
 | |
|     t.ok(it instanceof DeferredCtor)
 | |
| 
 | |
|     it.close(function (err) {
 | |
|       t.ifError(err, 'no close() error')
 | |
|       t.same(order, ['_open', privateMethod, '_close'])
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   const verifyClosed = function (t, it, method, cb) {
 | |
|     const requiredArgs = method === 'nextv' ? [10] : []
 | |
| 
 | |
|     it[method](...requiredArgs, function (err) {
 | |
|       t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN', `correct error on first ${method}()`)
 | |
| 
 | |
|       // Should account for userland code that ignores errors
 | |
|       it[method](...requiredArgs, function (err) {
 | |
|         t.is(err && err.code, 'LEVEL_ITERATOR_NOT_OPEN', `correct error on second ${method}()`)
 | |
|         cb()
 | |
|       })
 | |
|     })
 | |
|   }
 | |
| }
 |