420 lines
9.4 KiB
JavaScript
420 lines
9.4 KiB
JavaScript
'use strict'
|
|
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const SonicBoom = require('../')
|
|
const { file, runTests } = require('./helper')
|
|
const proxyquire = require('proxyquire')
|
|
|
|
runTests(buildTests)
|
|
|
|
function buildTests (test, sync) {
|
|
// Reset the unmask for testing
|
|
process.umask(0o000)
|
|
|
|
test('append', (t) => {
|
|
t.plan(4)
|
|
|
|
const dest = file()
|
|
fs.writeFileSync(dest, 'hello world\n')
|
|
const stream = new SonicBoom({ dest, append: false, sync })
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
t.ok(stream.write('something else\n'))
|
|
|
|
stream.flush()
|
|
|
|
stream.on('drain', () => {
|
|
fs.readFile(dest, 'utf8', (err, data) => {
|
|
t.error(err)
|
|
t.equal(data, 'something else\n')
|
|
stream.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
test('mkdir', (t) => {
|
|
t.plan(4)
|
|
|
|
const dest = path.join(file(), 'out.log')
|
|
const stream = new SonicBoom({ dest, mkdir: true, sync })
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
|
|
stream.flush()
|
|
|
|
stream.on('drain', () => {
|
|
fs.readFile(dest, 'utf8', (err, data) => {
|
|
t.error(err)
|
|
t.equal(data, 'hello world\n')
|
|
stream.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
test('flush', (t) => {
|
|
t.plan(5)
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({ fd, minLength: 4096, sync })
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
t.ok(stream.write('something else\n'))
|
|
|
|
stream.flush()
|
|
|
|
stream.on('drain', () => {
|
|
fs.readFile(dest, 'utf8', (err, data) => {
|
|
t.error(err)
|
|
t.equal(data, 'hello world\nsomething else\n')
|
|
stream.end()
|
|
})
|
|
})
|
|
})
|
|
|
|
test('flush with no data', (t) => {
|
|
t.plan(2)
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({ fd, minLength: 4096, sync })
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
stream.flush()
|
|
|
|
stream.on('drain', () => {
|
|
t.pass('drain emitted')
|
|
})
|
|
})
|
|
|
|
test('call flush cb after flushed', (t) => {
|
|
t.plan(4)
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({ fd, minLength: 4096, sync })
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
t.ok(stream.write('something else\n'))
|
|
|
|
stream.flush((err) => {
|
|
if (err) t.fail(err)
|
|
else t.pass('flush cb called')
|
|
})
|
|
})
|
|
|
|
test('only call fsyncSync and not fsync when fsync: true', (t) => {
|
|
t.plan(6)
|
|
|
|
const fakeFs = Object.create(fs)
|
|
const SonicBoom = proxyquire('../', {
|
|
fs: fakeFs
|
|
})
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({
|
|
fd,
|
|
sync,
|
|
fsync: true,
|
|
minLength: 4096
|
|
})
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
fakeFs.fsync = function (fd, cb) {
|
|
t.fail('fake fs.fsync called while should not')
|
|
cb()
|
|
}
|
|
fakeFs.fsyncSync = function (fd) {
|
|
t.pass('fake fsyncSync called')
|
|
}
|
|
|
|
function successOnAsyncOrSyncFn (isSync, originalFn) {
|
|
return function (...args) {
|
|
t.pass(`fake fs.${originalFn.name} called`)
|
|
fakeFs[originalFn.name] = originalFn
|
|
return fakeFs[originalFn.name](...args)
|
|
}
|
|
}
|
|
|
|
if (sync) {
|
|
fakeFs.writeSync = successOnAsyncOrSyncFn(true, fs.writeSync)
|
|
} else {
|
|
fakeFs.write = successOnAsyncOrSyncFn(false, fs.write)
|
|
}
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
stream.flush((err) => {
|
|
if (err) t.fail(err)
|
|
else t.pass('flush cb called')
|
|
|
|
process.nextTick(() => {
|
|
// to make sure fsync is not called as well
|
|
t.pass('nextTick after flush called')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('call flush cb with error when fsync failed', (t) => {
|
|
t.plan(5)
|
|
|
|
const fakeFs = Object.create(fs)
|
|
const SonicBoom = proxyquire('../', {
|
|
fs: fakeFs
|
|
})
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({
|
|
fd,
|
|
sync,
|
|
minLength: 4096
|
|
})
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
const err = new Error('other')
|
|
err.code = 'other'
|
|
|
|
function onFsyncOnFsyncSync (isSync, originalFn) {
|
|
return function (...args) {
|
|
Error.captureStackTrace(err)
|
|
t.pass(`fake fs.${originalFn.name} called`)
|
|
fakeFs[originalFn.name] = originalFn
|
|
const cb = args[args.length - 1]
|
|
|
|
cb(err)
|
|
}
|
|
}
|
|
|
|
// only one is called depends on sync
|
|
fakeFs.fsync = onFsyncOnFsyncSync(false, fs.fsync)
|
|
|
|
function successOnAsyncOrSyncFn (isSync, originalFn) {
|
|
return function (...args) {
|
|
t.pass(`fake fs.${originalFn.name} called`)
|
|
fakeFs[originalFn.name] = originalFn
|
|
return fakeFs[originalFn.name](...args)
|
|
}
|
|
}
|
|
|
|
if (sync) {
|
|
fakeFs.writeSync = successOnAsyncOrSyncFn(true, fs.writeSync)
|
|
} else {
|
|
fakeFs.write = successOnAsyncOrSyncFn(false, fs.write)
|
|
}
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
stream.flush((err) => {
|
|
if (err) t.equal(err.code, 'other')
|
|
else t.fail('flush cb called without an error')
|
|
})
|
|
})
|
|
|
|
test('call flush cb even when have no data', (t) => {
|
|
t.plan(2)
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({ fd, minLength: 4096, sync })
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
|
|
stream.flush((err) => {
|
|
if (err) t.fail(err)
|
|
else t.pass('flush cb called')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('call flush cb even when minLength is 0', (t) => {
|
|
t.plan(1)
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({ fd, minLength: 0, sync })
|
|
|
|
stream.flush((err) => {
|
|
if (err) t.fail(err)
|
|
else t.pass('flush cb called')
|
|
})
|
|
})
|
|
|
|
test('call flush cb with an error when trying to flush destroyed stream', (t) => {
|
|
t.plan(1)
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({ fd, minLength: 4096, sync })
|
|
stream.destroy()
|
|
|
|
stream.flush((err) => {
|
|
if (err) t.pass(err)
|
|
else t.fail('flush cb called without an error')
|
|
})
|
|
})
|
|
|
|
test('call flush cb with an error when failed to flush', (t) => {
|
|
t.plan(5)
|
|
|
|
const fakeFs = Object.create(fs)
|
|
const SonicBoom = proxyquire('../', {
|
|
fs: fakeFs
|
|
})
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({
|
|
fd,
|
|
sync,
|
|
minLength: 4096
|
|
})
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
const err = new Error('other')
|
|
err.code = 'other'
|
|
|
|
function onWriteOrWriteSync (isSync, originalFn) {
|
|
return function (...args) {
|
|
Error.captureStackTrace(err)
|
|
t.pass(`fake fs.${originalFn.name} called`)
|
|
fakeFs[originalFn.name] = originalFn
|
|
|
|
if (isSync) throw err
|
|
const cb = args[args.length - 1]
|
|
|
|
cb(err)
|
|
}
|
|
}
|
|
|
|
// only one is called depends on sync
|
|
fakeFs.write = onWriteOrWriteSync(false, fs.write)
|
|
fakeFs.writeSync = onWriteOrWriteSync(true, fs.writeSync)
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
stream.flush((err) => {
|
|
if (err) t.equal(err.code, 'other')
|
|
else t.fail('flush cb called without an error')
|
|
})
|
|
|
|
stream.end()
|
|
|
|
stream.on('close', () => {
|
|
t.pass('close emitted')
|
|
})
|
|
})
|
|
|
|
test('call flush cb when finish writing when currently in the middle', (t) => {
|
|
t.plan(4)
|
|
|
|
const fakeFs = Object.create(fs)
|
|
const SonicBoom = proxyquire('../', {
|
|
fs: fakeFs
|
|
})
|
|
|
|
const dest = file()
|
|
const fd = fs.openSync(dest, 'w')
|
|
const stream = new SonicBoom({
|
|
fd,
|
|
sync,
|
|
|
|
// to trigger write without calling flush
|
|
minLength: 1
|
|
})
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
function onWriteOrWriteSync (originalFn) {
|
|
return function (...args) {
|
|
stream.flush((err) => {
|
|
if (err) t.fail(err)
|
|
else t.pass('flush cb called')
|
|
})
|
|
|
|
t.pass(`fake fs.${originalFn.name} called`)
|
|
fakeFs[originalFn.name] = originalFn
|
|
return originalFn(...args)
|
|
}
|
|
}
|
|
|
|
// only one is called depends on sync
|
|
fakeFs.write = onWriteOrWriteSync(fs.write)
|
|
fakeFs.writeSync = onWriteOrWriteSync(fs.writeSync)
|
|
|
|
t.ok(stream.write('hello world\n'))
|
|
})
|
|
|
|
test('call flush cb when writing and trying to flush before ready (on async)', (t) => {
|
|
t.plan(4)
|
|
|
|
const fakeFs = Object.create(fs)
|
|
const SonicBoom = proxyquire('../', {
|
|
fs: fakeFs
|
|
})
|
|
|
|
fakeFs.open = fsOpen
|
|
|
|
const dest = file()
|
|
const stream = new SonicBoom({
|
|
fd: dest,
|
|
// only async as sync is part of the constructor so the user will not be able to call write/flush
|
|
// before ready
|
|
sync: false,
|
|
|
|
// to not trigger write without calling flush
|
|
minLength: 4096
|
|
})
|
|
|
|
stream.on('ready', () => {
|
|
t.pass('ready emitted')
|
|
})
|
|
|
|
function fsOpen (...args) {
|
|
process.nextTick(() => {
|
|
// try writing and flushing before ready and in the middle of opening
|
|
t.pass('fake fs.open called')
|
|
t.ok(stream.write('hello world\n'))
|
|
|
|
// calling flush
|
|
stream.flush((err) => {
|
|
if (err) t.fail(err)
|
|
else t.pass('flush cb called')
|
|
})
|
|
|
|
fakeFs.open = fs.open
|
|
fs.open(...args)
|
|
})
|
|
}
|
|
})
|
|
}
|