UNPKG

mountable-dwebtrie

Version:

A DWebTrie wrapper that supports mounting of other dWebTries.

681 lines (635 loc) 17.5 kB
const test = require('tape') const raf = require('random-access-file') const tmp = require('tmp') const { create } = require('./helpers/create') const { runAll } = require('./helpers/util') const MountableDWebTrie = require('..') test('simple single-trie get', async t => { const { tries } = await create(1) const [trie] = tries try { await runAll([ cb => trie.put('/a', 'hello', cb), cb => { trie.get('/a', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('simple two-trie get', async t => { const { tries } = await create(3) const [trie1, trie2, trie3] = tries try { await runAll([ cb => trie3.put('/c', 'hello', cb), cb => trie2.mount('/b', trie3.key, cb), cb => trie1.mount('/a', trie2.key, cb), cb => { trie1.get('/a/b/c', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('mounting a bad key returns an error', async t => { const { tries } = await create(1) const [trie] = tries try { await runAll([ cb => trie.put('/a', 'hello', cb), cb => trie.mount('/b', 'some bad key', cb) ]) } catch (err) { t.true(err.badKey) t.pass('threw an error') t.end() } }) test('force-mounting a bad key, then reading from that mountpoint produces an error', async t => { const { tries } = await create(1) const [trie] = tries try { await runAll([ cb => trie.put('/a', 'hello', cb), cb => trie.mount('/b', 'some bad key', { skipValidation: true }, cb), cb => trie.get('/b/c', cb) ]) } catch (err) { t.true(err.badKey) t.pass('threw an error') t.end() } }) test('versioned two-trie mount/remount', async t => { const { tries } = await create(2) const [trie1, trie2] = tries try { await runAll([ cb => trie2.put('/c', 'hello', cb), cb => trie1.mount('/b', trie2.key, { version: trie2.version }, cb), cb => trie2.put('/d', 'goodbye', cb), cb => { trie1.get('/b/d', (err, node) => { t.error(err, 'no error') t.false(node) return cb(null) }) }, cb => trie1.unmount('/b', cb), cb => trie1.mount('/b', trie2.key, cb), cb => { trie1.get('/b/d', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('goodbye')) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('nested versioned trie get + remount', async t => { const { tries } = await create(3) const [trie1, trie2, trie3] = tries try { await runAll([ cb => trie3.put('/c', 'hello', cb), cb => trie2.mount('/b', trie3.key, { version: trie3.version }, cb), cb => trie1.mount('/a', trie2.key, cb), cb => trie3.put('/d', 'goodbye', cb), cb => { trie1.get('/a/b/c', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb(null) }) }, cb => { trie1.get('/a/b/d', (err, node) => { t.error(err, 'no error') t.false(node) return cb(null) }) }, cb => trie2.unmount('/b', cb), cb => trie2.mount('/b', trie3.key, { version: trie3.version }, cb), cb => { trie1.get('/a/b/d', (err, node) => { t.error(err, 'no error') t.true(node) t.same(node.value, Buffer.from('goodbye')) t.end() }) }, ]) } catch (err) { t.fail(err) } }) test('simple cross-trie get', async t => { const { tries } = await create(2) const [rootTrie, subTrie] = tries try { await runAll([ cb => rootTrie.mount('/a', subTrie.key, cb), cb => rootTrie.put('/b', 'hello', cb), cb => subTrie.put('/b', 'goodbye', cb), cb => rootTrie.get('/a/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'a/b') t.same(node.value, Buffer.from('goodbye')) return cb(null) }), cb => rootTrie.get('/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'b') t.same(node.value, Buffer.from('hello')) return cb(null) }), cb => subTrie.get('/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'b') t.same(node.value, Buffer.from('goodbye')) return cb(null) }) ]) } catch (err) { t.error(err) } t.end() }) test('simple cross-trie del', async t => { const { tries } = await create(2) const [rootTrie, subTrie] = tries try { await runAll([ cb => rootTrie.mount('/a', subTrie.key, cb), cb => rootTrie.put('/b', 'hello', cb), cb => subTrie.put('/b', 'goodbye', cb), cb => rootTrie.get('/a/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'a/b') return cb(null) }), cb => rootTrie.get('/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'b') return cb(null) }), cb => subTrie.get('/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'b') return cb(null) }), cb => subTrie.del('/b', cb), cb => rootTrie.get('/a/b', (err, node) => { if (err) return cb(err) t.false(node) return cb(null) }), cb => rootTrie.del('/b', cb), cb => rootTrie.get('/b', (err, node) => { if (err) return cb(err) t.false(node) return cb(null) }), cb => subTrie.get('/b', (err, node) => { if (err) return cb(err) t.false(node) return cb(null) }) ]) } catch (err) { t.error(err) } t.end() }) test('recursive cross-trie put/get', async t => { const { tries } = await create(3) const [rootTrie, subTrie, subsubTrie] = tries try { await runAll([ cb => rootTrie.mount('/a', subTrie.key, cb), cb => subTrie.mount('/b', subsubTrie.key, cb), cb => rootTrie.put('/b', 'hello', cb), cb => subTrie.put('/c', 'dog', cb), cb => subTrie.put('/d', 'goodbye', cb), cb => subsubTrie.put('/d', 'cat', cb), cb => rootTrie.get('/a/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'a/d') t.same(node.value, Buffer.from('goodbye')) return cb(null) }), cb => rootTrie.get('/a/b/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'a/b/d') t.same(node.value, Buffer.from('cat')) return cb(null) }), cb => subsubTrie.get('/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'd') t.same(node.value, Buffer.from('cat')) return cb(null) }) ]) } catch (err) { t.error(err) } t.end() }) test('recursive cross-trie del', async t => { const { tries } = await create(3) const [rootTrie, subTrie, subsubTrie] = tries try { await runAll([ cb => rootTrie.mount('/a', subTrie.key, cb), cb => subTrie.mount('/b', subsubTrie.key, cb), cb => rootTrie.put('/b', 'hello', cb), cb => subTrie.put('/c', 'dog', cb), cb => subTrie.put('/d', 'goodbye', cb), cb => subsubTrie.put('/d', 'cat', cb), cb => subsubTrie.put('/e', 'walrus', cb), cb => subTrie.put('/c', 'potato', cb), cb => subTrie.put('/e', 'cat', cb), cb => subsubTrie.put('/f', 'horse', cb), cb => rootTrie.put('/d', 'calculator', cb), cb => rootTrie.del('/d', cb), cb => subsubTrie.del('/e', cb), cb => subTrie.del('/d', cb), cb => subTrie.get('/d', (err, node) => { if (err) return cb(err) t.false(node) return cb(null) }), cb => rootTrie.get('/a/b/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.value, Buffer.from('cat')) return cb(null) }), cb => subTrie.del('/b', cb), cb => rootTrie.get('/a/b/d', (err, node) => { if (err) return cb(err) t.false(node) return cb(null) }), cb => subsubTrie.get('/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node.key, 'd') t.same(node.value, Buffer.from('cat')) return cb(null) }), cb => rootTrie.get('/d', (err, node) => { t.error(err, 'no error') t.false(node) return cb(null) }) ]) } catch (err) { t.error(err) } t.end() }) test('recursive get node references the correct sub-trie', async t => { const { tries } = await create(3) const [rootTrie, subTrie, subsubTrie] = tries try { await runAll([ cb => rootTrie.mount('/a', subTrie.key, cb), cb => subTrie.mount('/b', subsubTrie.key, cb), cb => rootTrie.put('/b', 'hello', cb), cb => subTrie.put('/c', 'dog', cb), cb => subTrie.put('/d', 'goodbye', cb), cb => subsubTrie.put('/d', 'cat', cb), cb => rootTrie.get('/a/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node[MountableDWebTrie.Symbols.TRIE].key, subTrie.key) return cb(null) }), cb => rootTrie.get('/a/b/d', (err, node) => { if (err) return cb(err) t.true(node) t.same(node[MountableDWebTrie.Symbols.TRIE].key, subsubTrie.key) return cb(null) }), cb => rootTrie.get('/b', (err, node) => { if (err) return cb(err) t.true(node) t.same(node[MountableDWebTrie.Symbols.TRIE].key, rootTrie.key) return cb(null) }) ]) } catch (err) { t.error(err) } t.end() }) test('get on a checkout', async t => { const { tries } = await create(2) const [trie] = tries var checkout = null try { await runAll([ cb => trie.put('/a', 'hello', cb), cb => { const version = trie.version checkout = trie.checkout(version) return cb(null) }, cb => trie.del('/a', cb), cb => { trie.get('/a', (err, node) => { t.error(err, 'no error') t.same(node, null) return cb(null) }) }, cb => { checkout.get('/a', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('delete a mount', async t => { const { tries } = await create(3) const [trie1, trie2, trie3] = tries try { await runAll([ cb => trie3.put('/c', 'hello', cb), cb => trie2.mount('/b', trie3.key, cb), cb => trie1.mount('/a', trie2.key, cb), cb => { trie1.get('/a/b/c', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb(null) }) }, cb => { trie1.unmount('/a', err => { t.error(err, 'no error') return cb(null) }) }, cb => { trie1.get('/a/b/c', (err, node) => { t.error(err, 'no error') t.false(node) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('delete a deep mount', async t => { const { tries } = await create(3, { sameStore: true }) const [trie1, trie2, trie3] = tries try { await runAll([ cb => trie3.put('/c', 'hello', cb), cb => trie2.mount('/b', trie3.key, cb), cb => trie1.mount('/a', trie2.key, cb), cb => { trie1.get('/a/b/c', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb(null) }) }, cb => { trie1.unmount('/a/b', err => { t.error(err, 'no error') return cb(null) }) }, cb => { trie1.get('/a/b/c', (err, node) => { t.error(err, 'no error') t.false(node) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('delete a deep subdirectory within a mount', async t => { const { tries } = await create(3, { sameStore: true }) const [trie1, trie2, trie3] = tries try { await runAll([ cb => trie3.put('/c/d/e/f', 'hello', cb), cb => trie2.mount('/b', trie3.key, cb), cb => trie1.mount('/a', trie2.key, cb), cb => { trie1.get('/a/b/c/d/e/f', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb(null) }) }, cb => { trie1.del('/a/b/c/d/e/f', err => { t.error(err, 'no error') return cb(null) }) }, cb => { trie1.get('/a/b/c/d/e/f', (err, node) => { t.error(err, 'no error') t.false(node) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('can create a cyclic mount', async t => { const { tries } = await create(2) const [trie1, trie2] = tries try { await runAll([ cb => trie1.mount('/a', trie2.key, cb), cb => trie2.mount('/b', trie1.key, cb), cb => trie1.put('/c', 'hello', cb), cb => { trie1.get('/c', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb(null) }) }, cb => { trie1.get('a/b/c', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) t.end() }) } ]) } catch (err) { t.fail(err) } }) test('can overwrite value at mountpoint', async t => { const { tries } = await create(2, { sameStore: true }) const [trie1, trie2] = tries var mountNode = null try { await runAll([ cb => trie1.mount('/a', trie2.key, { value: 'hello' }, cb), cb => trie2.put('/b', 'b', cb), cb => trie1.get('/a', (err, node) => { t.error(err, 'no error') mountNode = node return cb(null) }), cb => trie1.put('/a', mountNode.value, { flags: mountNode.flags }, cb), cb => trie1.get('/a', (err, node) => { t.error(err, 'no error') t.same(node.flags, 1) t.same(node.value, Buffer.from('hello')) return cb(null) }), cb => trie1.get('/a/b', (err, node) => { t.error(err, 'no error') t.true(node) if (node) t.same(node.value, Buffer.from('b')) t.end() }) ]) } catch (err) { t.fail(err) } }) test.skip('deep mount reads', async t => { const DEPTH = 20 const { path, cleanup } = await new Promise((resolve, reject) => { tmp.dir((err, path, cleanup) => { if (err) return reject(err) return resolve({ path, cleanup }) }) }) console.log('path:', path) const storage = p => { return raf(path + '/' + p) } const { tries } = await create(DEPTH, { _storage: null, alwaysUpdate: false }) const ops = [] console.log('trie keys:', tries.map(t => t.key)) for (let i = 1; i < DEPTH; i++) { ops.push(cb => tries[i - 1].mount('/' + i, tries[i].key, cb)) ops.push(cb => tries[i - 1].put('/a', 'hello', cb)) } try { await runAll([ ...ops, /* cb => { console.time('t1') console.log(1) tries[0].get('/1/2/3/4/5/6/7/8/9/a', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) console.timeEnd('t1') return cb() }) }, cb => { console.time('t1') tries[0].get('/1/2/3/4/5/6/7/8/9/a', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) console.timeEnd('t1') return cb() }) }, cb => { console.time('t1') tries[0].get('/1/2/3/4/5/6/7/8/9/a', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) console.timeEnd('t1') return cb() }) }, */ cb => { console.time('t1') tries[0].get('/1/a', (err, node) => { console.timeEnd('t1') t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb() }) }, cb => { console.time('t1') tries[0].get('/1/a', (err, node) => { console.timeEnd('t1') t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb() }) }, cb => { console.time('t1') tries[0].get('/1/a', (err, node) => { console.timeEnd('t1') t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) return cb() }) }, cb => { console.time('t1') tries[0].get('/a', (err, node) => { t.error(err, 'no error') t.same(node.value, Buffer.from('hello')) console.timeEnd('t1') return cb() }) } ]) //cleanup() t.end() } catch (err) { t.fail(err) } })