549 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			549 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 'use strict'
 | |
| 
 | |
| const { Buffer } = require('buffer')
 | |
| const identity = (v) => v
 | |
| 
 | |
| let db
 | |
| 
 | |
| exports.setUp = function (test, testCommon) {
 | |
|   test('setUp db', function (t) {
 | |
|     db = testCommon.factory()
 | |
|     db.open(t.end.bind(t))
 | |
|   })
 | |
| }
 | |
| 
 | |
| exports.args = function (test, testCommon) {
 | |
|   for (const mode of ['iterator', 'keys', 'values']) {
 | |
|     test(`${mode}() has db reference`, async function (t) {
 | |
|       const it = db[mode]()
 | |
| 
 | |
|       // May return iterator of an underlying db, that's okay.
 | |
|       t.ok(it.db === db || it.db === (db.db || db._db || db))
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}() has limit and count properties`, async function (t) {
 | |
|       const iterators = [db[mode]()]
 | |
|       t.is(iterators[0].limit, Infinity, 'defaults to infinite')
 | |
| 
 | |
|       for (const limit of [-1, 0, 1, Infinity]) {
 | |
|         const it = db[mode]({ limit })
 | |
|         iterators.push(it)
 | |
|         t.is(it.limit, limit === -1 ? Infinity : limit, 'has limit property')
 | |
|       }
 | |
| 
 | |
|       t.ok(iterators.every(it => it.count === 0), 'has count property')
 | |
|       await Promise.all(iterators.map(it => it.close()))
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().nextv() yields error if size is invalid`, async function (t) {
 | |
|       t.plan(4)
 | |
| 
 | |
|       const it = db[mode]()
 | |
| 
 | |
|       for (const args of [[], [NaN], ['1'], [2.5]]) {
 | |
|         try {
 | |
|           await it.nextv(...args)
 | |
|         } catch (err) {
 | |
|           t.is(err.message, "The first argument 'size' must be an integer")
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
|   }
 | |
| }
 | |
| 
 | |
| exports.sequence = function (test, testCommon) {
 | |
|   for (const mode of ['iterator', 'keys', 'values']) {
 | |
|     test(`${mode}().close() is idempotent`, function (t) {
 | |
|       const iterator = db[mode]()
 | |
| 
 | |
|       iterator.close(function () {
 | |
|         let async = false
 | |
| 
 | |
|         iterator.close(function () {
 | |
|           t.ok(async, 'callback is asynchronous')
 | |
|           t.end()
 | |
|         })
 | |
| 
 | |
|         async = true
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     for (const method of ['next', 'nextv', 'all']) {
 | |
|       const requiredArgs = method === 'nextv' ? [1] : []
 | |
| 
 | |
|       test(`${mode}().${method}() after close() yields error`, function (t) {
 | |
|         const iterator = db[mode]()
 | |
|         iterator.close(function (err) {
 | |
|           t.error(err)
 | |
| 
 | |
|           let async = false
 | |
| 
 | |
|           iterator[method](...requiredArgs, function (err2) {
 | |
|             t.ok(err2, 'returned error')
 | |
|             t.is(err2.code, 'LEVEL_ITERATOR_NOT_OPEN', 'correct message')
 | |
|             t.ok(async, 'callback is asynchronous')
 | |
|             t.end()
 | |
|           })
 | |
| 
 | |
|           async = true
 | |
|         })
 | |
|       })
 | |
| 
 | |
|       for (const otherMethod of ['next', 'nextv', 'all']) {
 | |
|         const otherRequiredArgs = otherMethod === 'nextv' ? [1] : []
 | |
| 
 | |
|         test(`${mode}().${method}() while busy with ${otherMethod}() yields error`, function (t) {
 | |
|           const iterator = db[mode]()
 | |
|           iterator[otherMethod](...otherRequiredArgs, function (err) {
 | |
|             t.error(err)
 | |
|             iterator.close(function (err) {
 | |
|               t.error(err)
 | |
|               t.end()
 | |
|             })
 | |
|           })
 | |
| 
 | |
|           let async = false
 | |
| 
 | |
|           iterator[method](...requiredArgs, function (err) {
 | |
|             t.ok(err, 'returned error')
 | |
|             t.is(err.code, 'LEVEL_ITERATOR_BUSY')
 | |
|             t.ok(async, 'callback is asynchronous')
 | |
|           })
 | |
| 
 | |
|           async = true
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (const deferred of [false, true]) {
 | |
|     for (const mode of ['iterator', 'keys', 'values']) {
 | |
|       for (const method of ['next', 'nextv', 'all']) {
 | |
|         const requiredArgs = method === 'nextv' ? [10] : []
 | |
| 
 | |
|         // NOTE: adapted from leveldown
 | |
|         test(`${mode}().${method}() after db.close() yields error (deferred: ${deferred})`, async function (t) {
 | |
|           t.plan(2)
 | |
| 
 | |
|           const db = testCommon.factory()
 | |
|           if (!deferred) await db.open()
 | |
| 
 | |
|           await db.put('a', 'a')
 | |
|           await db.put('b', 'b')
 | |
| 
 | |
|           const it = db[mode]()
 | |
| 
 | |
|           // The first call *should* succeed, because it was scheduled before close(). However, success
 | |
|           // is not a must. Because nextv() and all() fallback to next*(), they're allowed to fail. An
 | |
|           // implementation can also choose to abort any pending call on close.
 | |
|           let promise = it[method](...requiredArgs).then(() => {
 | |
|             t.pass('Optionally succeeded')
 | |
|           }).catch((err) => {
 | |
|             t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
 | |
|           })
 | |
| 
 | |
|           // The second call *must* fail, because it was scheduled after close()
 | |
|           promise = promise.then(() => {
 | |
|             return it[method](...requiredArgs).then(() => {
 | |
|               t.fail('Expected an error')
 | |
|             }).catch((err) => {
 | |
|               t.is(err.code, 'LEVEL_ITERATOR_NOT_OPEN')
 | |
|             })
 | |
|           })
 | |
| 
 | |
|           return Promise.all([db.close(), promise])
 | |
|         })
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| exports.iterator = function (test, testCommon) {
 | |
|   test('test simple iterator()', function (t) {
 | |
|     const data = [
 | |
|       { type: 'put', key: 'foobatch1', value: 'bar1' },
 | |
|       { type: 'put', key: 'foobatch2', value: 'bar2' },
 | |
|       { type: 'put', key: 'foobatch3', value: 'bar3' }
 | |
|     ]
 | |
|     let idx = 0
 | |
| 
 | |
|     db.batch(data, function (err) {
 | |
|       t.error(err)
 | |
|       const iterator = db.iterator()
 | |
|       const fn = function (err, key, value) {
 | |
|         t.error(err)
 | |
|         if (key && value) {
 | |
|           t.is(key, data[idx].key, 'correct key')
 | |
|           t.is(value, data[idx].value, 'correct value')
 | |
|           db.nextTick(next)
 | |
|           idx++
 | |
|         } else { // end
 | |
|           t.ok(err == null, 'err argument is nullish')
 | |
|           t.ok(typeof key === 'undefined', 'key argument is undefined')
 | |
|           t.ok(typeof value === 'undefined', 'value argument is undefined')
 | |
|           t.is(idx, data.length, 'correct number of entries')
 | |
|           iterator.close(function () {
 | |
|             t.end()
 | |
|           })
 | |
|         }
 | |
|       }
 | |
|       const next = function () {
 | |
|         iterator.next(fn)
 | |
|       }
 | |
| 
 | |
|       next()
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   // NOTE: adapted from leveldown
 | |
|   test('key-only iterator', function (t) {
 | |
|     const it = db.iterator({ values: false })
 | |
| 
 | |
|     it.next(function (err, key, value) {
 | |
|       t.ifError(err, 'no next() error')
 | |
|       t.is(key, 'foobatch1')
 | |
|       t.is(value, undefined)
 | |
|       it.close(t.end.bind(t))
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   // NOTE: adapted from leveldown
 | |
|   test('value-only iterator', function (t) {
 | |
|     const it = db.iterator({ keys: false })
 | |
| 
 | |
|     it.next(function (err, key, value) {
 | |
|       t.ifError(err, 'no next() error')
 | |
|       t.is(key, undefined)
 | |
|       t.is(value, 'bar1')
 | |
|       it.close(t.end.bind(t))
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   test('db.keys().next()', function (t) {
 | |
|     const it = db.keys()
 | |
| 
 | |
|     it.next(function (err, key) {
 | |
|       t.ifError(err, 'no next() error')
 | |
|       t.is(key, 'foobatch1')
 | |
|       it.close(t.end.bind(t))
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   test('db.values().next()', function (t) {
 | |
|     const it = db.values()
 | |
| 
 | |
|     it.next(function (err, value) {
 | |
|       t.ifError(err, 'no next() error')
 | |
|       t.is(value, 'bar1')
 | |
|       it.close(t.end.bind(t))
 | |
|     })
 | |
|   })
 | |
| 
 | |
|   for (const mode of ['iterator', 'keys', 'values']) {
 | |
|     const mapEntry = e => mode === 'iterator' ? e : mode === 'keys' ? e[0] : e[1]
 | |
| 
 | |
|     test(`${mode}().nextv()`, async function (t) {
 | |
|       const it = db[mode]()
 | |
| 
 | |
|       t.same(await it.nextv(1), [['foobatch1', 'bar1']].map(mapEntry))
 | |
|       t.same(await it.nextv(2, {}), [['foobatch2', 'bar2'], ['foobatch3', 'bar3']].map(mapEntry))
 | |
|       t.same(await it.nextv(2), [])
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().nextv() in reverse`, async function (t) {
 | |
|       const it = db[mode]({ reverse: true })
 | |
| 
 | |
|       t.same(await it.nextv(1), [['foobatch3', 'bar3']].map(mapEntry))
 | |
|       t.same(await it.nextv(2, {}), [['foobatch2', 'bar2'], ['foobatch1', 'bar1']].map(mapEntry))
 | |
|       t.same(await it.nextv(2), [])
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().nextv() has soft minimum of 1`, async function (t) {
 | |
|       const it = db[mode]()
 | |
| 
 | |
|       t.same(await it.nextv(0), [['foobatch1', 'bar1']].map(mapEntry))
 | |
|       t.same(await it.nextv(0), [['foobatch2', 'bar2']].map(mapEntry))
 | |
|       t.same(await it.nextv(0, {}), [['foobatch3', 'bar3']].map(mapEntry))
 | |
|       t.same(await it.nextv(0), [])
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().nextv() requesting more than available`, async function (t) {
 | |
|       const it = db[mode]()
 | |
| 
 | |
|       t.same(await it.nextv(10), [
 | |
|         ['foobatch1', 'bar1'],
 | |
|         ['foobatch2', 'bar2'],
 | |
|         ['foobatch3', 'bar3']
 | |
|       ].map(mapEntry))
 | |
|       t.same(await it.nextv(10), [])
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().nextv() honors limit`, async function (t) {
 | |
|       const it = db[mode]({ limit: 2 })
 | |
| 
 | |
|       t.same(await it.nextv(10), [['foobatch1', 'bar1'], ['foobatch2', 'bar2']].map(mapEntry))
 | |
|       t.same(await it.nextv(10), [])
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().nextv() honors limit in reverse`, async function (t) {
 | |
|       const it = db[mode]({ limit: 2, reverse: true })
 | |
| 
 | |
|       t.same(await it.nextv(10), [['foobatch3', 'bar3'], ['foobatch2', 'bar2']].map(mapEntry))
 | |
|       t.same(await it.nextv(10), [])
 | |
| 
 | |
|       await it.close()
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().all()`, async function (t) {
 | |
|       t.same(await db[mode]().all(), [
 | |
|         ['foobatch1', 'bar1'],
 | |
|         ['foobatch2', 'bar2'],
 | |
|         ['foobatch3', 'bar3']
 | |
|       ].map(mapEntry))
 | |
| 
 | |
|       t.same(await db[mode]().all({}), [
 | |
|         ['foobatch1', 'bar1'],
 | |
|         ['foobatch2', 'bar2'],
 | |
|         ['foobatch3', 'bar3']
 | |
|       ].map(mapEntry))
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().all() in reverse`, async function (t) {
 | |
|       t.same(await db[mode]({ reverse: true }).all(), [
 | |
|         ['foobatch3', 'bar3'],
 | |
|         ['foobatch2', 'bar2'],
 | |
|         ['foobatch1', 'bar1']
 | |
|       ].map(mapEntry))
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().all() honors limit`, async function (t) {
 | |
|       t.same(await db[mode]({ limit: 2 }).all(), [
 | |
|         ['foobatch1', 'bar1'],
 | |
|         ['foobatch2', 'bar2']
 | |
|       ].map(mapEntry))
 | |
| 
 | |
|       const it = db[mode]({ limit: 2 })
 | |
| 
 | |
|       t.same(await it.next(), mapEntry(['foobatch1', 'bar1']))
 | |
|       t.same(await it.all(), [['foobatch2', 'bar2']].map(mapEntry))
 | |
|     })
 | |
| 
 | |
|     test(`${mode}().all() honors limit in reverse`, async function (t) {
 | |
|       t.same(await db[mode]({ limit: 2, reverse: true }).all(), [
 | |
|         ['foobatch3', 'bar3'],
 | |
|         ['foobatch2', 'bar2']
 | |
|       ].map(mapEntry))
 | |
| 
 | |
|       const it = db[mode]({ limit: 2, reverse: true })
 | |
| 
 | |
|       t.same(await it.next(), mapEntry(['foobatch3', 'bar3']))
 | |
|       t.same(await it.all(), [['foobatch2', 'bar2']].map(mapEntry))
 | |
|     })
 | |
|   }
 | |
| 
 | |
|   // NOTE: adapted from memdown
 | |
|   test('iterator() sorts lexicographically', async function (t) {
 | |
|     const db = testCommon.factory()
 | |
|     await db.open()
 | |
| 
 | |
|     // Write in unsorted order with multiple operations
 | |
|     await db.put('f', 'F')
 | |
|     await db.put('a', 'A')
 | |
|     await db.put('~', '~')
 | |
|     await db.put('e', 'E')
 | |
|     await db.put('🐄', '🐄')
 | |
|     await db.batch([
 | |
|       { type: 'put', key: 'd', value: 'D' },
 | |
|       { type: 'put', key: 'b', value: 'B' },
 | |
|       { type: 'put', key: 'ff', value: 'FF' },
 | |
|       { type: 'put', key: 'a🐄', value: 'A🐄' }
 | |
|     ])
 | |
|     await db.batch([
 | |
|       { type: 'put', key: '', value: 'empty' },
 | |
|       { type: 'put', key: '2', value: '2' },
 | |
|       { type: 'put', key: '12', value: '12' },
 | |
|       { type: 'put', key: '\t', value: '\t' }
 | |
|     ])
 | |
| 
 | |
|     t.same(await db.iterator().all(), [
 | |
|       ['', 'empty'],
 | |
|       ['\t', '\t'],
 | |
|       ['12', '12'],
 | |
|       ['2', '2'],
 | |
|       ['a', 'A'],
 | |
|       ['a🐄', 'A🐄'],
 | |
|       ['b', 'B'],
 | |
|       ['d', 'D'],
 | |
|       ['e', 'E'],
 | |
|       ['f', 'F'],
 | |
|       ['ff', 'FF'],
 | |
|       ['~', '~'],
 | |
|       ['🐄', '🐄']
 | |
|     ])
 | |
| 
 | |
|     t.same(await db.iterator({ lte: '' }).all(), [
 | |
|       ['', 'empty']
 | |
|     ])
 | |
| 
 | |
|     return db.close()
 | |
|   })
 | |
| 
 | |
|   for (const keyEncoding of ['buffer', 'view']) {
 | |
|     if (!testCommon.supports.encodings[keyEncoding]) continue
 | |
| 
 | |
|     test(`test iterator() has byte order (${keyEncoding} encoding)`, function (t) {
 | |
|       const db = testCommon.factory({ keyEncoding })
 | |
| 
 | |
|       db.open(function (err) {
 | |
|         t.ifError(err, 'no open() error')
 | |
| 
 | |
|         const ctor = keyEncoding === 'buffer' ? Buffer : Uint8Array
 | |
|         const keys = [2, 11, 1].map(b => ctor.from([b]))
 | |
| 
 | |
|         db.batch(keys.map((key) => ({ type: 'put', key, value: 'x' })), function (err) {
 | |
|           t.ifError(err, 'no batch() error')
 | |
| 
 | |
|           db.keys().all(function (err, keys) {
 | |
|             t.ifError(err, 'no all() error')
 | |
|             t.same(keys.map(k => k[0]), [1, 2, 11], 'order is ok')
 | |
| 
 | |
|             db.iterator().all(function (err, entries) {
 | |
|               t.ifError(err, 'no all() error')
 | |
|               t.same(entries.map(e => e[0][0]), [1, 2, 11], 'order is ok')
 | |
| 
 | |
|               db.close(t.end.bind(t))
 | |
|             })
 | |
|           })
 | |
|         })
 | |
|       })
 | |
|     })
 | |
| 
 | |
|     // NOTE: adapted from memdown and level-js
 | |
|     test(`test iterator() with byte range (${keyEncoding} encoding)`, async function (t) {
 | |
|       const db = testCommon.factory({ keyEncoding })
 | |
|       await db.open()
 | |
| 
 | |
|       await db.put(Uint8Array.from([0x0]), '0')
 | |
|       await db.put(Uint8Array.from([128]), '128')
 | |
|       await db.put(Uint8Array.from([160]), '160')
 | |
|       await db.put(Uint8Array.from([192]), '192')
 | |
| 
 | |
|       const collect = async (range) => {
 | |
|         const entries = await db.iterator(range).all()
 | |
|         t.ok(entries.every(e => e[0] instanceof Uint8Array)) // True for both encodings
 | |
|         t.ok(entries.every(e => e[1] === String(e[0][0])))
 | |
|         return entries.map(e => e[0][0])
 | |
|       }
 | |
| 
 | |
|       t.same(await collect({ gt: Uint8Array.from([255]) }), [])
 | |
|       t.same(await collect({ gt: Uint8Array.from([192]) }), [])
 | |
|       t.same(await collect({ gt: Uint8Array.from([160]) }), [192])
 | |
|       t.same(await collect({ gt: Uint8Array.from([128]) }), [160, 192])
 | |
|       t.same(await collect({ gt: Uint8Array.from([0x0]) }), [128, 160, 192])
 | |
|       t.same(await collect({ gt: Uint8Array.from([]) }), [0x0, 128, 160, 192])
 | |
| 
 | |
|       t.same(await collect({ lt: Uint8Array.from([255]) }), [0x0, 128, 160, 192])
 | |
|       t.same(await collect({ lt: Uint8Array.from([192]) }), [0x0, 128, 160])
 | |
|       t.same(await collect({ lt: Uint8Array.from([160]) }), [0x0, 128])
 | |
|       t.same(await collect({ lt: Uint8Array.from([128]) }), [0x0])
 | |
|       t.same(await collect({ lt: Uint8Array.from([0x0]) }), [])
 | |
|       t.same(await collect({ lt: Uint8Array.from([]) }), [])
 | |
| 
 | |
|       t.same(await collect({ gte: Uint8Array.from([255]) }), [])
 | |
|       t.same(await collect({ gte: Uint8Array.from([192]) }), [192])
 | |
|       t.same(await collect({ gte: Uint8Array.from([160]) }), [160, 192])
 | |
|       t.same(await collect({ gte: Uint8Array.from([128]) }), [128, 160, 192])
 | |
|       t.same(await collect({ gte: Uint8Array.from([0x0]) }), [0x0, 128, 160, 192])
 | |
|       t.same(await collect({ gte: Uint8Array.from([]) }), [0x0, 128, 160, 192])
 | |
| 
 | |
|       t.same(await collect({ lte: Uint8Array.from([255]) }), [0x0, 128, 160, 192])
 | |
|       t.same(await collect({ lte: Uint8Array.from([192]) }), [0x0, 128, 160, 192])
 | |
|       t.same(await collect({ lte: Uint8Array.from([160]) }), [0x0, 128, 160])
 | |
|       t.same(await collect({ lte: Uint8Array.from([128]) }), [0x0, 128])
 | |
|       t.same(await collect({ lte: Uint8Array.from([0x0]) }), [0x0])
 | |
|       t.same(await collect({ lte: Uint8Array.from([]) }), [])
 | |
| 
 | |
|       return db.close()
 | |
|     })
 | |
|   }
 | |
| }
 | |
| 
 | |
| exports.decode = function (test, testCommon) {
 | |
|   for (const deferred of [false, true]) {
 | |
|     for (const mode of ['iterator', 'keys', 'values']) {
 | |
|       for (const method of ['next', 'nextv', 'all']) {
 | |
|         const requiredArgs = method === 'nextv' ? [1] : []
 | |
| 
 | |
|         for (const encodingOption of ['keyEncoding', 'valueEncoding']) {
 | |
|           if (mode === 'keys' && encodingOption === 'valueEncoding') continue
 | |
|           if (mode === 'values' && encodingOption === 'keyEncoding') continue
 | |
| 
 | |
|           // NOTE: adapted from encoding-down
 | |
|           test(`${mode}().${method}() catches decoding error from ${encodingOption} (deferred: ${deferred})`, async function (t) {
 | |
|             t.plan(4)
 | |
| 
 | |
|             const encoding = {
 | |
|               format: 'utf8',
 | |
|               decode: function (x) {
 | |
|                 t.is(x, encodingOption === 'keyEncoding' ? 'testKey' : 'testValue')
 | |
|                 throw new Error('from encoding')
 | |
|               },
 | |
|               encode: identity
 | |
|             }
 | |
| 
 | |
|             const db = testCommon.factory()
 | |
|             await db.put('testKey', 'testValue')
 | |
| 
 | |
|             if (deferred) {
 | |
|               await db.close()
 | |
|               db.open(t.ifError.bind(t))
 | |
|             } else {
 | |
|               t.pass('non-deferred')
 | |
|             }
 | |
| 
 | |
|             const it = db[mode]({ [encodingOption]: encoding })
 | |
| 
 | |
|             try {
 | |
|               await it[method](...requiredArgs)
 | |
|             } catch (err) {
 | |
|               t.is(err.code, 'LEVEL_DECODE_ERROR')
 | |
|               t.is(err.cause && err.cause.message, 'from encoding')
 | |
|             }
 | |
| 
 | |
|             return db.close()
 | |
|           })
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| exports.tearDown = function (test, testCommon) {
 | |
|   test('tearDown', function (t) {
 | |
|     db.close(t.end.bind(t))
 | |
|   })
 | |
| }
 | |
| 
 | |
| exports.all = function (test, testCommon) {
 | |
|   exports.setUp(test, testCommon)
 | |
|   exports.args(test, testCommon)
 | |
|   exports.sequence(test, testCommon)
 | |
|   exports.iterator(test, testCommon)
 | |
|   exports.decode(test, testCommon)
 | |
|   exports.tearDown(test, testCommon)
 | |
| }
 |