strong-tie/inbound-calls
0
1'use strict'2 3const fs = require('fs')4const path = require('path')5const SonicBoom = require('../')6const { file, runTests } = require('./helper')7const proxyquire = require('proxyquire')8 9runTests(buildTests)10 11function buildTests (test, sync) {12 // Reset the unmask for testing13 process.umask(0o000)14 15 test('append', (t) => {16 t.plan(4)17 18 const dest = file()19 fs.writeFileSync(dest, 'hello world\n')20 const stream = new SonicBoom({ dest, append: false, sync })21 22 stream.on('ready', () => {23 t.pass('ready emitted')24 })25 26 t.ok(stream.write('something else\n'))27 28 stream.flush()29 30 stream.on('drain', () => {31 fs.readFile(dest, 'utf8', (err, data) => {32 t.error(err)33 t.equal(data, 'something else\n')34 stream.end()35 })36 })37 })38 39 test('mkdir', (t) => {40 t.plan(4)41 42 const dest = path.join(file(), 'out.log')43 const stream = new SonicBoom({ dest, mkdir: true, sync })44 45 stream.on('ready', () => {46 t.pass('ready emitted')47 })48 49 t.ok(stream.write('hello world\n'))50 51 stream.flush()52 53 stream.on('drain', () => {54 fs.readFile(dest, 'utf8', (err, data) => {55 t.error(err)56 t.equal(data, 'hello world\n')57 stream.end()58 })59 })60 })61 62 test('flush', (t) => {63 t.plan(5)64 65 const dest = file()66 const fd = fs.openSync(dest, 'w')67 const stream = new SonicBoom({ fd, minLength: 4096, sync })68 69 stream.on('ready', () => {70 t.pass('ready emitted')71 })72 73 t.ok(stream.write('hello world\n'))74 t.ok(stream.write('something else\n'))75 76 stream.flush()77 78 stream.on('drain', () => {79 fs.readFile(dest, 'utf8', (err, data) => {80 t.error(err)81 t.equal(data, 'hello world\nsomething else\n')82 stream.end()83 })84 })85 })86 87 test('flush with no data', (t) => {88 t.plan(2)89 90 const dest = file()91 const fd = fs.openSync(dest, 'w')92 const stream = new SonicBoom({ fd, minLength: 4096, sync })93 94 stream.on('ready', () => {95 t.pass('ready emitted')96 })97 98 stream.flush()99 100 stream.on('drain', () => {101 t.pass('drain emitted')102 })103 })104 105 test('call flush cb after flushed', (t) => {106 t.plan(4)107 108 const dest = file()109 const fd = fs.openSync(dest, 'w')110 const stream = new SonicBoom({ fd, minLength: 4096, sync })111 112 stream.on('ready', () => {113 t.pass('ready emitted')114 })115 116 t.ok(stream.write('hello world\n'))117 t.ok(stream.write('something else\n'))118 119 stream.flush((err) => {120 if (err) t.fail(err)121 else t.pass('flush cb called')122 })123 })124 125 test('only call fsyncSync and not fsync when fsync: true', (t) => {126 t.plan(6)127 128 const fakeFs = Object.create(fs)129 const SonicBoom = proxyquire('../', {130 fs: fakeFs131 })132 133 const dest = file()134 const fd = fs.openSync(dest, 'w')135 const stream = new SonicBoom({136 fd,137 sync,138 fsync: true,139 minLength: 4096140 })141 142 stream.on('ready', () => {143 t.pass('ready emitted')144 })145 146 fakeFs.fsync = function (fd, cb) {147 t.fail('fake fs.fsync called while should not')148 cb()149 }150 fakeFs.fsyncSync = function (fd) {151 t.pass('fake fsyncSync called')152 }153 154 function successOnAsyncOrSyncFn (isSync, originalFn) {155 return function (...args) {156 t.pass(`fake fs.${originalFn.name} called`)157 fakeFs[originalFn.name] = originalFn158 return fakeFs[originalFn.name](...args)159 }160 }161 162 if (sync) {163 fakeFs.writeSync = successOnAsyncOrSyncFn(true, fs.writeSync)164 } else {165 fakeFs.write = successOnAsyncOrSyncFn(false, fs.write)166 }167 168 t.ok(stream.write('hello world\n'))169 stream.flush((err) => {170 if (err) t.fail(err)171 else t.pass('flush cb called')172 173 process.nextTick(() => {174 // to make sure fsync is not called as well175 t.pass('nextTick after flush called')176 })177 })178 })179 180 test('call flush cb with error when fsync failed', (t) => {181 t.plan(5)182 183 const fakeFs = Object.create(fs)184 const SonicBoom = proxyquire('../', {185 fs: fakeFs186 })187 188 const dest = file()189 const fd = fs.openSync(dest, 'w')190 const stream = new SonicBoom({191 fd,192 sync,193 minLength: 4096194 })195 196 stream.on('ready', () => {197 t.pass('ready emitted')198 })199 200 const err = new Error('other')201 err.code = 'other'202 203 function onFsyncOnFsyncSync (isSync, originalFn) {204 return function (...args) {205 Error.captureStackTrace(err)206 t.pass(`fake fs.${originalFn.name} called`)207 fakeFs[originalFn.name] = originalFn208 const cb = args[args.length - 1]209 210 cb(err)211 }212 }213 214 // only one is called depends on sync215 fakeFs.fsync = onFsyncOnFsyncSync(false, fs.fsync)216 217 function successOnAsyncOrSyncFn (isSync, originalFn) {218 return function (...args) {219 t.pass(`fake fs.${originalFn.name} called`)220 fakeFs[originalFn.name] = originalFn221 return fakeFs[originalFn.name](...args)222 }223 }224 225 if (sync) {226 fakeFs.writeSync = successOnAsyncOrSyncFn(true, fs.writeSync)227 } else {228 fakeFs.write = successOnAsyncOrSyncFn(false, fs.write)229 }230 231 t.ok(stream.write('hello world\n'))232 stream.flush((err) => {233 if (err) t.equal(err.code, 'other')234 else t.fail('flush cb called without an error')235 })236 })237 238 test('call flush cb even when have no data', (t) => {239 t.plan(2)240 241 const dest = file()242 const fd = fs.openSync(dest, 'w')243 const stream = new SonicBoom({ fd, minLength: 4096, sync })244 245 stream.on('ready', () => {246 t.pass('ready emitted')247 248 stream.flush((err) => {249 if (err) t.fail(err)250 else t.pass('flush cb called')251 })252 })253 })254 255 test('call flush cb even when minLength is 0', (t) => {256 t.plan(1)257 258 const dest = file()259 const fd = fs.openSync(dest, 'w')260 const stream = new SonicBoom({ fd, minLength: 0, sync })261 262 stream.flush((err) => {263 if (err) t.fail(err)264 else t.pass('flush cb called')265 })266 })267 268 test('call flush cb with an error when trying to flush destroyed stream', (t) => {269 t.plan(1)270 271 const dest = file()272 const fd = fs.openSync(dest, 'w')273 const stream = new SonicBoom({ fd, minLength: 4096, sync })274 stream.destroy()275 276 stream.flush((err) => {277 if (err) t.pass(err)278 else t.fail('flush cb called without an error')279 })280 })281 282 test('call flush cb with an error when failed to flush', (t) => {283 t.plan(5)284 285 const fakeFs = Object.create(fs)286 const SonicBoom = proxyquire('../', {287 fs: fakeFs288 })289 290 const dest = file()291 const fd = fs.openSync(dest, 'w')292 const stream = new SonicBoom({293 fd,294 sync,295 minLength: 4096296 })297 298 stream.on('ready', () => {299 t.pass('ready emitted')300 })301 302 const err = new Error('other')303 err.code = 'other'304 305 function onWriteOrWriteSync (isSync, originalFn) {306 return function (...args) {307 Error.captureStackTrace(err)308 t.pass(`fake fs.${originalFn.name} called`)309 fakeFs[originalFn.name] = originalFn310 311 if (isSync) throw err312 const cb = args[args.length - 1]313 314 cb(err)315 }316 }317 318 // only one is called depends on sync319 fakeFs.write = onWriteOrWriteSync(false, fs.write)320 fakeFs.writeSync = onWriteOrWriteSync(true, fs.writeSync)321 322 t.ok(stream.write('hello world\n'))323 stream.flush((err) => {324 if (err) t.equal(err.code, 'other')325 else t.fail('flush cb called without an error')326 })327 328 stream.end()329 330 stream.on('close', () => {331 t.pass('close emitted')332 })333 })334 335 test('call flush cb when finish writing when currently in the middle', (t) => {336 t.plan(4)337 338 const fakeFs = Object.create(fs)339 const SonicBoom = proxyquire('../', {340 fs: fakeFs341 })342 343 const dest = file()344 const fd = fs.openSync(dest, 'w')345 const stream = new SonicBoom({346 fd,347 sync,348 349 // to trigger write without calling flush350 minLength: 1351 })352 353 stream.on('ready', () => {354 t.pass('ready emitted')355 })356 357 function onWriteOrWriteSync (originalFn) {358 return function (...args) {359 stream.flush((err) => {360 if (err) t.fail(err)361 else t.pass('flush cb called')362 })363 364 t.pass(`fake fs.${originalFn.name} called`)365 fakeFs[originalFn.name] = originalFn366 return originalFn(...args)367 }368 }369 370 // only one is called depends on sync371 fakeFs.write = onWriteOrWriteSync(fs.write)372 fakeFs.writeSync = onWriteOrWriteSync(fs.writeSync)373 374 t.ok(stream.write('hello world\n'))375 })376 377 test('call flush cb when writing and trying to flush before ready (on async)', (t) => {378 t.plan(4)379 380 const fakeFs = Object.create(fs)381 const SonicBoom = proxyquire('../', {382 fs: fakeFs383 })384 385 fakeFs.open = fsOpen386 387 const dest = file()388 const stream = new SonicBoom({389 fd: dest,390 // only async as sync is part of the constructor so the user will not be able to call write/flush391 // before ready392 sync: false,393 394 // to not trigger write without calling flush395 minLength: 4096396 })397 398 stream.on('ready', () => {399 t.pass('ready emitted')400 })401 402 function fsOpen (...args) {403 process.nextTick(() => {404 // try writing and flushing before ready and in the middle of opening405 t.pass('fake fs.open called')406 t.ok(stream.write('hello world\n'))407 408 // calling flush409 stream.flush((err) => {410 if (err) t.fail(err)411 else t.pass('flush cb called')412 })413 414 fakeFs.open = fs.open415 fs.open(...args)416 })417 }418 })419}420 