@web4/bitdrive
Version:
Bitdrive is a secure, real time distributed file system
552 lines (503 loc) • 14.4 kB
JavaScript
const test = require('tape')
const ram = require('random-access-memory')
const Chainstore = require('@web4/chainstore')
const Replicator = require('./helpers/replicator')
const create = require('./helpers/create')
test('single-file download', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
function onready () {
drive1.writeFile('hello', 'world', err => {
t.error(err, 'no error')
setImmediate(() => {
drive2.stats('hello', (err, totals) => {
t.error(err, 'no error')
t.same(totals.blocks, 1)
t.same(totals.downloadedBlocks, 0)
drive2.download('hello', err => {
t.error(err)
drive2.stats('hello', (err, totals) => {
t.same(totals.downloadedBlocks, 1)
r.end()
})
})
})
})
})
}
})
test('download completion calls callback if provided', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
function onready () {
drive1.writeFile('hello', 'world', err => {
t.error(err, 'no error')
setImmediate(() => {
drive2.stats('hello', (err, totals) => {
t.error(err, 'no error')
t.same(totals.blocks, 1)
t.same(totals.downloadedBlocks, 0)
const handle = drive2.download('hello', onfinished)
ondownloading(handle)
})
})
})
}
function onfinished () {
drive2.stats('hello', (err, totals) => {
t.same(totals.downloadedBlocks, 1)
r.end()
})
}
function ondownloading (handle) {
handle.on('error', t.fail.bind(t))
handle.on('cancel', t.fail.bind(t))
}
})
test('directory download', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
function onready () {
drive1.writeFile('a/1', '1', err => {
t.error(err, 'no error')
drive1.writeFile('a/2', '2',err => {
t.error(err, 'no error')
drive1.writeFile('a/3', '3', err => {
t.error(err, 'no error')
drive2.download('a', { maxConcurrent: 1 }, err => {
drive2.stats('a', (err, totals) => {
t.error(err, 'no error')
t.same(totals.get('/a/1').downloadedBlocks, 1)
t.same(totals.get('/a/2').downloadedBlocks, 1)
t.same(totals.get('/a/3').downloadedBlocks, 1)
r.end()
})
})
})
})
})
}
})
test('download cancellation', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2, { throttle: 50 })
onready()
})
})
function onready () {
const writeStream = drive1.createWriteStream('a')
var chunks = 100
return write()
function write () {
writeStream.write(Buffer.alloc(1024 * 1024).fill('abcdefg'), err => {
if (err) return t.fail(err)
if (--chunks) return write()
return writeStream.end(() => {
return onwritten()
})
})
}
}
function onwritten () {
const handle = drive2.download('a', { detailed: true, statsInterval: 50 }, err => {
if (err) t.fail(err)
drive2.stats('a', (err, totals) => {
t.error(err, 'no error')
t.true(totals.downloadedBlocks > 0 && totals.downloadedBlocks < 100)
r.end()
})
})
drive2.getContent((err, content) => {
t.error(err, 'no error')
content.once('download', () => {
handle.destroy()
})
})
}
})
test('download omits mounts by default', t => {
const r = new Replicator(t)
const store = new Chainstore(ram)
var drive1, mount, drive2
store.ready(() => {
drive1 = create({ chainstore: store, namespace: 'd1' })
mount = create({ chainstore: store, namespace: 'd2' })
drive1.ready(() => {
mount.ready(() => {
drive2 = create(drive1.key)
drive1.mount('b', mount.key, err => {
t.error(err)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
})
})
})
function onready () {
mount.writeFile('hello', 'world', err => {
t.error(err)
drive1.writeFile('a/1', '1', err => {
t.error(err, 'no error')
drive1.writeFile('a/2', '2',err => {
t.error(err, 'no error')
drive1.writeFile('a/3', '3', err => {
t.error(err, 'no error')
drive2.download('/', { maxConcurrent: 1 }, err => {
drive2.stats('a', (err, totals) => {
t.error(err, 'no error')
t.same(totals.get('/a/1').downloadedBlocks, 1)
t.same(totals.get('/a/2').downloadedBlocks, 1)
t.same(totals.get('/a/3').downloadedBlocks, 1)
drive2.stats('b', (err, totals) => {
t.error(err, 'no error')
t.same(totals.get('/b/hello').downloadedBlocks, 0)
r.end()
})
})
})
})
})
})
})
}
})
test('download with noMounts false includes mounts', t => {
const r = new Replicator(t)
const store = new Chainstore(ram)
var drive1, mount, drive2
store.ready(() => {
drive1 = create({ chainstore: store, namespace: 'd1' })
mount = create({ chainstore: store, namespace: 'd2' })
drive1.ready(() => {
mount.ready(() => {
drive2 = create(drive1.key)
drive1.mount('b', mount.key, err => {
t.error(err)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
})
})
})
function onready () {
mount.writeFile('hello', 'world', err => {
t.error(err)
drive1.writeFile('a/1', '1', err => {
t.error(err, 'no error')
drive1.writeFile('a/2', '2',err => {
t.error(err, 'no error')
drive1.writeFile('a/3', '3', err => {
t.error(err, 'no error')
drive2.download('/', { maxConcurrent: 1, noMounts: false }, err => {
drive2.stats('a', (err, totals) => {
t.error(err, 'no error')
t.same(totals.get('/a/1').downloadedBlocks, 1)
t.same(totals.get('/a/2').downloadedBlocks, 1)
t.same(totals.get('/a/3').downloadedBlocks, 1)
drive2.stats('b', (err, totals) => {
t.error(err, 'no error')
t.same(totals.get('/b/hello').downloadedBlocks, 1)
r.end()
})
})
})
})
})
})
})
}
})
test('drive mirroring', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
function onready () {
drive1.writeFile('hello', 'world', () => {
drive1.writeFile('hello', 'world2', () => {
drive1.writeFile('hello', 'world3', () => {
drive2.mirror()
setImmediate(() => {
onmirroring()
})
})
})
})
}
function onmirroring () {
drive2.getContent((err, content) => {
t.error(err, 'no error')
// All versions of 'hello' should have been synced.
t.same(content.downloaded(), 3)
t.end()
})
}
})
test('can cancel a mirror', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
function onready () {
drive1.writeFile('hello', 'world', () => {
drive1.writeFile('hello', 'world2', () => {
const unmirror = drive2.mirror()
setImmediate(() => {
unmirror()
drive1.writeFile('hello', 'world3', () => {
setImmediate(() => {
onmirroring()
})
})
})
})
})
}
function onmirroring () {
drive2.getContent((err, content) => {
t.error(err, 'no error')
// Only the first two versions of 'hello' should have been synced.
t.same(content.downloaded(), 2)
t.end()
})
}
})
test('calling mirror is idempotent', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
function onready () {
drive1.writeFile('hello', 'world', () => {
drive1.writeFile('hello', 'world2', () => {
drive1.writeFile('hello', 'world3', () => {
drive2.mirror()
drive2.mirror()
drive2.mirror()
setImmediate(() => {
onmirroring()
})
})
})
})
}
function onmirroring () {
drive2.getContent((err, content) => {
t.error(err, 'no error')
// All versions of 'hello' should have been synced.
t.same(content.downloaded(), 3)
t.same(drive2.listeners('content-feed').length, 1)
t.same(drive2.listeners('metadata-feed').length, 1)
t.end()
})
}
})
test('mirroring also mirrors mounts', t => {
const r = new Replicator(t)
const store = new Chainstore(ram)
var drive1, mount, drive2
store.ready(() => {
drive1 = create({ chainstore: store, namespace: 'd1' })
mount = create({ chainstore: store, namespace: 'd2' })
drive1.ready(() => {
mount.ready(() => {
drive2 = create(drive1.key)
drive1.mount('b', mount.key, err => {
t.error(err)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
})
})
})
function onready () {
mount.writeFile('hello', 'world', () => {
mount.writeFile('hello', 'world2', () => {
drive1.writeFile('a', '1', () => {
drive1.writeFile('a', '2', () => {
drive2.mirror()
setImmediate(() => {
onmirroring()
})
})
})
})
})
}
function onmirroring () {
drive2.getAllMounts((err, mounts) => {
t.error(err, 'no error')
const root = mounts.get('/')
const bMount = mounts.get('/b')
t.same(root.content.downloaded(), 2)
t.same(bMount.content.downloaded(), 2)
t.end()
})
}
})
test('mirroring mirrors dynamically-added mounts', t => {
const r = new Replicator(t)
const store = new Chainstore(ram)
var drive1, mount, drive2
store.ready(() => {
drive1 = create({ chainstore: store, namespace: 'd1' })
mount = create({ chainstore: store, namespace: 'd2' })
drive1.ready(() => {
mount.ready(() => {
drive2 = create(drive1.key)
drive2.mirror()
setImmediate(() => {
drive1.mount('b', mount.key, err => {
t.error(err)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
onready()
})
})
})
})
})
})
function onready () {
mount.writeFile('hello', 'world', () => {
mount.writeFile('hello', 'world2', () => {
drive1.writeFile('a', '1', () => {
drive1.writeFile('a', '2', () => {
drive2.mirror()
setImmediate(() => {
onmirroring()
})
})
})
})
})
}
function onmirroring () {
drive2.getAllMounts((err, mounts) => {
t.error(err, 'no error')
const root = mounts.get('/')
const bMount = mounts.get('/b')
t.same(root.content.downloaded(), 2)
t.same(bMount.content.downloaded(), 2)
t.end()
})
}
})
test('last unmirror will clean up if mirror is called many times', t => {
const r = new Replicator(t)
const drive1 = create()
var drive2 = null
var unmirror = null
var initialCount = null
drive1.ready(err => {
t.error(err, 'no error')
drive2 = create(drive1.key)
drive2.ready(err => {
t.error(err, 'no error')
r.replicate(drive1, drive2)
initialCount = drive2.listenerCount('metadata-feed')
onready()
})
})
function onready () {
drive1.writeFile('hello', 'world', () => {
drive1.writeFile('hello', 'world2', () => {
drive1.writeFile('hello', 'world3', () => {
drive2.mirror()
drive2.mirror()
unmirror = drive2.mirror()
const mirrorCount = drive2.listenerCount('metadata-feed')
t.same(mirrorCount, initialCount + 1)
setImmediate(() => {
onmirroring()
})
})
})
})
}
function onmirroring () {
drive2.getContent((err, content) => {
t.same(content.downloaded(), 3)
unmirror()
t.same(drive2.listenerCount('metadata-feed'), initialCount)
t.end()
})
}
})
function printHandle (handle) {
handle.on('start', (...args) => console.log('start', args))
handle.on('progress', (...args) => console.log('progress', args))
handle.on('error', (...args) => console.log('error', args))
handle.on('finish', (...args) => console.log('finish', args))
}