| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- 'use strict'
- process.setMaxListeners(1000000);
- const fs = require('fs')
- const {test} = require('tap')
- const requireInject = require('require-inject')
- // defining mock for fs so its functions can be modified
- const fsMock = Object.assign ( {}, fs, {
- /* ASYNC */
- mkdir (filename, opts, cb) {
- return cb(null);
- },
- realpath (filename, cb) {
- return cb(null, filename)
- },
- open (tmpfile, options, mode, cb) {
- if (/noopen/.test(tmpfile)) return cb(new Error('ENOOPEN'))
- cb(null, tmpfile)
- },
- write (fd) {
- const cb = arguments[arguments.length - 1]
- if (/nowrite/.test(fd)) return cb(new Error('ENOWRITE'))
- cb()
- },
- fsync (fd, cb) {
- if (/nofsync/.test(fd)) return cb(new Error('ENOFSYNC'))
- cb()
- },
- close (fd, cb) {
- cb()
- },
- chown (tmpfile, uid, gid, cb) {
- if (/nochown/.test(tmpfile)) return cb(new Error('ENOCHOWN'))
- cb()
- },
- chmod (tmpfile, mode, cb) {
- if (/nochmod/.test(tmpfile)) return cb(new Error('ENOCHMOD'))
- cb()
- },
- rename (tmpfile, filename, cb) {
- if (/norename/.test(tmpfile)) return cb(new Error('ENORENAME'))
- cb()
- },
- unlink (tmpfile, cb) {
- if (/nounlink/.test(tmpfile)) return cb(new Error('ENOUNLINK'))
- cb()
- },
- stat (tmpfile, cb) {
- if (/nostat/.test(tmpfile)) return cb(new Error('ENOSTAT'))
- cb()
- },
- /* SYNC */
- mkdirSync (filename) {},
- realpathSync (filename, cb) {
- return filename
- },
- openSync (tmpfile, options) {
- if (/noopen/.test(tmpfile)) throw new Error('ENOOPEN')
- return tmpfile
- },
- writeSync (fd) {
- if (/nowrite/.test(fd)) throw new Error('ENOWRITE')
- },
- fsyncSync (fd) {
- if (/nofsync/.test(fd)) throw new Error('ENOFSYNC')
- },
- closeSync () {},
- chownSync (tmpfile, uid, gid) {
- if (/nochown/.test(tmpfile)) throw new Error('ENOCHOWN')
- },
- chmodSync (tmpfile, mode) {
- if (/nochmod/.test(tmpfile)) throw new Error('ENOCHMOD')
- },
- renameSync (tmpfile, filename) {
- if (/norename/.test(tmpfile)) throw new Error('ENORENAME')
- },
- unlinkSync (tmpfile) {
- if (/nounlink/.test(tmpfile)) throw new Error('ENOUNLINK')
- },
- statSync (tmpfile) {
- if (/nostat/.test(tmpfile)) throw new Error('ENOSTAT')
- }
- })
- const {writeFile: writeFileAtomic} = requireInject('../dist', { fs: fsMock });
- // preserve original functions
- const oldRealPath = fsMock.realpath
- const oldRename = fsMock.rename
- test('ensure writes to the same file are serial', t => {
- let fileInUse = false
- const ops = 5 // count for how many concurrent write ops to request
- t.plan(ops * 3 + 3)
- fsMock.realpath = (...args) => {
- t.false(fileInUse, 'file not in use')
- fileInUse = true
- oldRealPath(...args)
- }
- fsMock.rename = (...args) => {
- t.true(fileInUse, 'file in use')
- fileInUse = false
- oldRename(...args)
- }
- const {writeFile: writeFileAtomic} = requireInject('../dist', { fs: fsMock });
- for (let i = 0; i < ops; i++) {
- writeFileAtomic('test', 'test', err => {
- if (err) t.fail(err)
- else t.pass('wrote without error')
- })
- }
- setTimeout(() => {
- writeFileAtomic('test', 'test', err => {
- if (err) t.fail(err)
- else t.pass('successive writes after delay')
- })
- }, 500)
- })
- test('allow write to multiple files in parallel, but same file writes are serial', t => {
- const filesInUse = []
- const ops = 5
- let wasParallel = false
- fsMock.realpath = (filename, ...args) => {
- filesInUse.push(filename)
- const firstOccurence = filesInUse.indexOf(filename)
- t.equal(filesInUse.indexOf(filename, firstOccurence + 1), -1, 'serial writes') // check for another occurence after the first
- if (filesInUse.length > 1) wasParallel = true // remember that a parallel operation took place
- oldRealPath(filename, ...args)
- }
- fsMock.rename = (filename, ...args) => {
- filesInUse.splice(filesInUse.indexOf(filename), 1)
- oldRename(filename, ...args)
- }
- const {writeFile: writeFileAtomic} = requireInject('../dist', { fs: fsMock });
- t.plan(ops * 2 * 2 + 1)
- let opCount = 0
- for (let i = 0; i < ops; i++) {
- writeFileAtomic('test', 'test', err => {
- if (err) t.fail(err, 'wrote without error')
- else t.pass('wrote without error')
- })
- writeFileAtomic('test2', 'test', err => {
- opCount++
- if (opCount === ops) t.true(wasParallel, 'parallel writes')
- if (err) t.fail(err, 'wrote without error')
- else t.pass('wrote without error')
- })
- }
- })
|