From 1006807de99bc060dc755ea7d9c1ee0a0990ba45 Mon Sep 17 00:00:00 2001 From: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> Date: Mon, 13 Jun 2022 17:16:43 +0200 Subject: [PATCH] Fix atomic createWriteStream (#8194) --- .eslintignore | 1 - .mocharc.json | 2 +- flow-libs/fs-write-stream-atomic.js.flow | 24 --- gulpfile.js | 1 - packages/core/fs/package.json | 1 - packages/core/fs/src/NodeFS.js | 68 +++++- .../utils/fs-write-stream-atomic/.travis.yml | 9 - packages/utils/fs-write-stream-atomic/LICENSE | 15 -- .../utils/fs-write-stream-atomic/README.md | 34 --- .../fixtures/thread-id-test-worker.js | 3 - .../utils/fs-write-stream-atomic/index.js | 198 ------------------ .../utils/fs-write-stream-atomic/package.json | 28 --- .../fs-write-stream-atomic/test/basic.js | 100 --------- .../fs-write-stream-atomic/test/chown.js | 48 ----- .../test/rename-eperm.js | 154 -------------- .../test/rename-fail.js | 30 --- .../fs-write-stream-atomic/test/slow-close.js | 43 ---- .../fs-write-stream-atomic/test/thread-id.js | 34 --- .../fs-write-stream-atomic/test/toolong.js | 33 --- .../utils/fs-write-stream-atomic/thread-id.js | 16 -- yarn.lock | 6 +- 21 files changed, 64 insertions(+), 784 deletions(-) delete mode 100644 flow-libs/fs-write-stream-atomic.js.flow delete mode 100644 packages/utils/fs-write-stream-atomic/.travis.yml delete mode 100644 packages/utils/fs-write-stream-atomic/LICENSE delete mode 100644 packages/utils/fs-write-stream-atomic/README.md delete mode 100644 packages/utils/fs-write-stream-atomic/fixtures/thread-id-test-worker.js delete mode 100644 packages/utils/fs-write-stream-atomic/index.js delete mode 100644 packages/utils/fs-write-stream-atomic/package.json delete mode 100644 packages/utils/fs-write-stream-atomic/test/basic.js delete mode 100644 packages/utils/fs-write-stream-atomic/test/chown.js delete mode 100644 packages/utils/fs-write-stream-atomic/test/rename-eperm.js delete mode 100644 packages/utils/fs-write-stream-atomic/test/rename-fail.js delete mode 100644 packages/utils/fs-write-stream-atomic/test/slow-close.js delete mode 100644 packages/utils/fs-write-stream-atomic/test/thread-id.js delete mode 100644 packages/utils/fs-write-stream-atomic/test/toolong.js delete mode 100644 packages/utils/fs-write-stream-atomic/thread-id.js diff --git a/.eslintignore b/.eslintignore index ecdd08ec0c9..b00c520f537 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,7 +10,6 @@ packages/*/*/test/integration/** packages/*/*/test/mochareporters.json packages/core/integration-tests/test/input/** packages/core/utils/test/input/** -packages/utils/fs-write-stream-atomic/** packages/utils/create-react-app/templates packages/examples diff --git a/.mocharc.json b/.mocharc.json index 1c4228840d2..5b18837e463 100644 --- a/.mocharc.json +++ b/.mocharc.json @@ -1,5 +1,5 @@ { - "spec": "packages/*/!(integration-tests|fs-write-stream-atomic)/test/*.js", + "spec": "packages/*/!(integration-tests)/test/*.js", "require": ["@parcel/babel-register", "@parcel/test-utils/src/mochaSetup.js"], // TODO: Remove this when https://github.com/nodejs/node/pull/28788 is resolved "exit": true diff --git a/flow-libs/fs-write-stream-atomic.js.flow b/flow-libs/fs-write-stream-atomic.js.flow deleted file mode 100644 index 933bd154f78..00000000000 --- a/flow-libs/fs-write-stream-atomic.js.flow +++ /dev/null @@ -1,24 +0,0 @@ -// @flow - -// Derived from the README and source of fs-write-stream-atomic located at -// https://github.com/npm/fs-write-stream-atomic -// Which is licensed ISC - -declare module 'fs-write-stream-atomic' { - import type {Writable} from 'stream'; - - declare type FsWriteStreamAtomicOptions = $Shape<{| - chown: $Shape<{| - uid: number, - gid: number, - |}>, - encoding: buffer$Encoding, - mode: number, - flags: string, - |}>; - - declare module.exports: ( - filename: string, - options: FsWriteStreamAtomicOptions, - ) => Writable; -} diff --git a/gulpfile.js b/gulpfile.js index 3b61057ad4d..e67919724f5 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -20,7 +20,6 @@ const IGNORED_PACKAGES = [ '!packages/core/utils/**', '!packages/reporters/cli/**', '!packages/reporters/dev-server/**', - '!packages/utils/fs-write-stream-atomic/**', ]; const paths = { diff --git a/packages/core/fs/package.json b/packages/core/fs/package.json index 2801fe79681..4addfde0c44 100644 --- a/packages/core/fs/package.json +++ b/packages/core/fs/package.json @@ -55,7 +55,6 @@ "@parcel/workers": "2.6.0" }, "devDependencies": { - "@parcel/fs-write-stream-atomic": "2.6.0", "graceful-fs": "^4.2.4", "ncp": "^2.0.0", "nullthrows": "^1.1.1", diff --git a/packages/core/fs/src/NodeFS.js b/packages/core/fs/src/NodeFS.js index 60be6425f35..263d036fc05 100644 --- a/packages/core/fs/src/NodeFS.js +++ b/packages/core/fs/src/NodeFS.js @@ -14,7 +14,7 @@ import nativeFS from 'fs'; import ncp from 'ncp'; import {promisify} from 'util'; import {registerSerializableClass} from '@parcel/core'; -import fsWriteStreamAtomic from '@parcel/fs-write-stream-atomic'; +import {hashStream} from '@parcel/utils'; import watcher from '@parcel/watcher'; import packageJSON from '../package.json'; @@ -58,7 +58,64 @@ export class NodeFS implements FileSystem { : searchNative.findFirstFile; createWriteStream(filePath: string, options: any): Writable { - return fsWriteStreamAtomic(filePath, options); + // Make createWriteStream atomic + let tmpFilePath = getTempFilePath(filePath); + let failed = false; + + const move = async () => { + if (!failed) { + try { + await fs.promises.rename(tmpFilePath, filePath); + } catch (e) { + // This is adapted from fs-write-stream-atomic. Apparently + // Windows doesn't like renaming when the target already exists. + if ( + process.platform === 'win32' && + e.syscall && + e.syscall === 'rename' && + e.code && + e.code === 'EPERM' + ) { + let [hashTmp, hashTarget] = await Promise.all([ + hashStream(writeStream.__atomicTmp), + hashStream(writeStream.__atomicTarget), + ]); + + await this.unlink(writeStream.__atomicTmp); + + if (hashTmp != hashTarget) { + throw e; + } + } + } + } + }; + + let writeStream = fs.createWriteStream(tmpFilePath, { + ...options, + fs: { + ...fs, + close: (fd, cb) => { + fs.close(fd, err => { + if (err) { + cb(err); + } else { + move().then( + () => cb(), + err => cb(err), + ); + } + }); + }, + }, + }); + + writeStream.once('error', () => { + failed = true; + fs.unlinkSync(tmpFilePath); + }); + + return writeStream; } async writeFile( @@ -67,12 +124,7 @@ export class NodeFS implements FileSystem { options: ?FileOptions, ): Promise { let tmpFilePath = getTempFilePath(filePath); - await fs.promises.writeFile( - tmpFilePath, - contents, - // $FlowFixMe - options, - ); + await fs.promises.writeFile(tmpFilePath, contents, options); await fs.promises.rename(tmpFilePath, filePath); } diff --git a/packages/utils/fs-write-stream-atomic/.travis.yml b/packages/utils/fs-write-stream-atomic/.travis.yml deleted file mode 100644 index 77d55c78f0c..00000000000 --- a/packages/utils/fs-write-stream-atomic/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: node_js -sudo: false -before_install: - - 'npm -g install npm' -node_js: - - '10' - - '8' - - '6' - - '11' diff --git a/packages/utils/fs-write-stream-atomic/LICENSE b/packages/utils/fs-write-stream-atomic/LICENSE deleted file mode 100644 index 19129e315fe..00000000000 --- a/packages/utils/fs-write-stream-atomic/LICENSE +++ /dev/null @@ -1,15 +0,0 @@ -The ISC License - -Copyright (c) Isaac Z. Schlueter and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR -IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/packages/utils/fs-write-stream-atomic/README.md b/packages/utils/fs-write-stream-atomic/README.md deleted file mode 100644 index 53d2cc74bae..00000000000 --- a/packages/utils/fs-write-stream-atomic/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# fs-write-stream-atomic - -Like `fs.createWriteStream(...)`, but atomic. - -Writes to a tmp file and does an atomic `fs.rename` to move it into -place when it's done. - -First rule of debugging: **It's always a race condition.** - -## USAGE - -```javascript -var fsWriteStreamAtomic = require('fs-write-stream-atomic'); -// options are optional. -var write = fsWriteStreamAtomic('output.txt', options); -var read = fs.createReadStream('input.txt'); -read.pipe(write); - -// When the write stream emits a 'finish' or 'close' event, -// you can be sure that it is moved into place, and contains -// all the bytes that were written to it, even if something else -// was writing to `output.txt` at the same time. -``` - -### `fsWriteStreamAtomic(filename, [options])` - -- `filename` {String} The file we want to write to -- `options` {Object} - - `chown` {Object} User and group to set ownership after write - - `uid` {Number} - - `gid` {Number} - - `encoding` {String} default = 'utf8' - - `mode` {Number} default = `0666` - - `flags` {String} default = `'w'` diff --git a/packages/utils/fs-write-stream-atomic/fixtures/thread-id-test-worker.js b/packages/utils/fs-write-stream-atomic/fixtures/thread-id-test-worker.js deleted file mode 100644 index af7bc79a2e1..00000000000 --- a/packages/utils/fs-write-stream-atomic/fixtures/thread-id-test-worker.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -require('worker_threads').parentPort.postMessage(require('../thread-id')); diff --git a/packages/utils/fs-write-stream-atomic/index.js b/packages/utils/fs-write-stream-atomic/index.js deleted file mode 100644 index 849ea300260..00000000000 --- a/packages/utils/fs-write-stream-atomic/index.js +++ /dev/null @@ -1,198 +0,0 @@ -var fs = require('graceful-fs'); -var Writable = require('stream').Writable; -var util = require('util'); -var MurmurHash3 = require('imurmurhash'); -var iferr = require('iferr'); -var crypto = require('crypto'); -var threadId = require('./thread-id'); - -function murmurhex() { - var hash = MurmurHash3(''); - for (var ii = 0; ii < arguments.length; ++ii) { - hash.hash('' + arguments[ii]); - } - return hash.result(); -} - -var invocations = 0; -function getTmpname(filename) { - return ( - filename + '.' + murmurhex(__filename, process.pid, threadId, ++invocations) - ); -} - -var setImmediate = global.setImmediate || setTimeout; - -module.exports = WriteStreamAtomic; - -// Requirements: -// 1. Write everything written to the stream to a temp file. -// 2. If there are no errors: -// a. moves the temp file into its final destination -// b. emits `finish` & `closed` ONLY after the file is -// fully flushed and renamed. -// 3. If there's an error, removes the temp file. - -util.inherits(WriteStreamAtomic, Writable); -function WriteStreamAtomic(path, options) { - if (!(this instanceof WriteStreamAtomic)) { - return new WriteStreamAtomic(path, options); - } - Writable.call(this, options); - - this.__isWin = - options && options.hasOwnProperty('isWin') - ? options.isWin - : process.platform === 'win32'; - - this.__atomicTarget = path; - this.__atomicTmp = getTmpname(path); - - this.__atomicChown = options && options.chown; - - this.__atomicClosed = false; - - this.__atomicStream = fs.WriteStream(this.__atomicTmp, options); - - this.__atomicStream.once('open', handleOpen(this)); - this.__atomicStream.once('close', handleClose(this)); - this.__atomicStream.once('error', handleError(this)); -} - -// We have to suppress default finish emitting, because ordinarily it -// would happen as soon as `end` is called on us and all of the -// data has been written to our target stream. So we suppress -// finish from being emitted here, and only emit it after our -// target stream is closed and we've moved everything around. -WriteStreamAtomic.prototype.emit = function (event) { - if (event === 'finish') return this.__atomicStream.end(); - return Writable.prototype.emit.apply(this, arguments); -}; - -WriteStreamAtomic.prototype._write = function (buffer, encoding, cb) { - var flushed = this.__atomicStream.write(buffer, encoding); - if (flushed) return cb(); - this.__atomicStream.once('drain', cb); -}; - -function handleOpen(writeStream) { - return function (fd) { - writeStream.emit('open', fd); - }; -} - -function handleClose(writeStream) { - return function () { - if (writeStream.__atomicClosed) return; - writeStream.__atomicClosed = true; - if (writeStream.__atomicChown) { - var uid = writeStream.__atomicChown.uid; - var gid = writeStream.__atomicChown.gid; - return fs.chown( - writeStream.__atomicTmp, - uid, - gid, - iferr(cleanup, moveIntoPlace), - ); - } else { - moveIntoPlace(); - } - }; - - function moveIntoPlace() { - fs.rename( - writeStream.__atomicTmp, - writeStream.__atomicTarget, - iferr(trapWindowsEPERM, end), - ); - } - - function trapWindowsEPERM(err) { - if ( - writeStream.__isWin && - err.syscall && - err.syscall === 'rename' && - err.code && - err.code === 'EPERM' - ) { - checkFileHashes(err); - } else { - cleanup(err); - } - } - - function checkFileHashes(eperm) { - var inprocess = 2; - var tmpFileHash = crypto.createHash('sha512'); - var targetFileHash = crypto.createHash('sha512'); - - fs.createReadStream(writeStream.__atomicTmp) - .on('data', function (data, enc) { - tmpFileHash.update(data, enc); - }) - .on('error', fileHashError) - .on('end', fileHashComplete); - fs.createReadStream(writeStream.__atomicTarget) - .on('data', function (data, enc) { - targetFileHash.update(data, enc); - }) - .on('error', fileHashError) - .on('end', fileHashComplete); - - function fileHashError() { - if (inprocess === 0) return; - inprocess = 0; - cleanup(eperm); - } - - function fileHashComplete() { - if (inprocess === 0) return; - if (--inprocess) return; - if (tmpFileHash.digest('hex') === targetFileHash.digest('hex')) { - return cleanup(); - } else { - return cleanup(eperm); - } - } - } - - function cleanup(err) { - fs.unlink(writeStream.__atomicTmp, function () { - if (err) { - writeStream.emit('error', err); - writeStream.emit('close'); - } else { - end(); - } - }); - } - - function end() { - // We have to use our parent class directly because we suppress `finish` - // events fired via our own emit method. - Writable.prototype.emit.call(writeStream, 'finish'); - - // Delay the close to provide the same temporal separation a physical - // file operation would have– that is, the close event is emitted only - // after the async close operation completes. - setImmediate(function () { - writeStream.emit('close'); - }); - } -} - -function handleError(writeStream) { - return function (er) { - cleanupSync(); - writeStream.emit('error', er); - writeStream.__atomicClosed = true; - writeStream.emit('close'); - }; - function cleanupSync() { - try { - fs.unlinkSync(writeStream.__atomicTmp); - } finally { - return; - } - } -} diff --git a/packages/utils/fs-write-stream-atomic/package.json b/packages/utils/fs-write-stream-atomic/package.json deleted file mode 100644 index 2e64056ea1a..00000000000 --- a/packages/utils/fs-write-stream-atomic/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@parcel/fs-write-stream-atomic", - "version": "2.6.0", - "description": "Like `fs.createWriteStream(...)`, but atomic.", - "main": "index.js", - "repository": { - "type": "git", - "url": "https://github.com/parcel-bundler/parcel.git", - "directory": "packages/utils/fs-write-stream-atomic" - }, - "directories": { - "test": "test" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^1.0.2", - "imurmurhash": "^0.1.4" - }, - "author": "Isaac Z. Schlueter (http://blog.izs.me/)", - "license": "ISC" -} diff --git a/packages/utils/fs-write-stream-atomic/test/basic.js b/packages/utils/fs-write-stream-atomic/test/basic.js deleted file mode 100644 index af3d6f438ba..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/basic.js +++ /dev/null @@ -1,100 +0,0 @@ -var fs = require('graceful-fs'); -var test = require('tap').test; -var path = require('path'); -var writeStream = require('../index.js'); - -var rename = fs.rename; -fs.rename = function (from, to, cb) { - setTimeout(function () { - rename(from, to, cb); - }, 100); -}; - -test('basic', function (t) { - // open 10 write streams to the same file. - // then write to each of them, and to the target - // and verify at the end that each of them does their thing - var target = path.resolve(__dirname, 'test.txt'); - var n = 10; - - // We run all of our assertions twice: - // once for finish, once for close - // There are 6 assertions, two fixed, plus 4 lines in the file. - t.plan(n * 2 * 6); - - var streams = []; - for (var i = 0; i < n; i++) { - var s = writeStream(target); - s.on('finish', verifier('finish', i)); - s.on('close', verifier('close', i)); - streams.push(s); - } - - function verifier(ev, num) { - return function () { - if (ev === 'close') { - t.equal(this.__emittedFinish, true, num + '. closed only after finish'); - } else { - this.__emittedFinish = true; - t.equal(ev, 'finish', num + '. finished'); - } - - // make sure that one of the atomic streams won. - var res = fs.readFileSync(target, 'utf8'); - var lines = res.trim().split(/\n/); - lines.forEach(function (line, lineno) { - var first = lines[0].match(/\d+$/)[0]; - var cur = line.match(/\d+$/)[0]; - t.equal(cur, first, num + '. line ' + lineno + ' matches'); - }); - - var resExpr = - /^first write \d+\nsecond write \d+\nthird write \d+\nfinal write \d+\n$/; - t.similar(res, resExpr, num + '. content matches'); - }; - } - - // now write something to each stream. - streams.forEach(function (stream, i) { - stream.write('first write ' + i + '\n'); - }); - - // wait a sec for those writes to go out. - setTimeout(function () { - // write something else to the target. - fs.writeFileSync(target, 'brutality!\n'); - - // write some more stuff. - streams.forEach(function (stream, i) { - stream.write('second write ' + i + '\n'); - }); - - setTimeout(function () { - // Oops! Deleted the file! - fs.unlinkSync(target); - - // write some more stuff. - streams.forEach(function (stream, i) { - stream.write('third write ' + i + '\n'); - }); - - setTimeout(function () { - fs.writeFileSync(target, 'brutality TWO!\n'); - streams.forEach(function (stream, i) { - stream.end('final write ' + i + '\n'); - }); - }, 50); - }, 50); - }, 50); -}); - -test('cleanup', function (t) { - fs.readdirSync(__dirname) - .filter(function (f) { - return f.match(/^test.txt/); - }) - .forEach(function (file) { - fs.unlinkSync(path.resolve(__dirname, file)); - }); - t.end(); -}); diff --git a/packages/utils/fs-write-stream-atomic/test/chown.js b/packages/utils/fs-write-stream-atomic/test/chown.js deleted file mode 100644 index 8aea481f4e7..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/chown.js +++ /dev/null @@ -1,48 +0,0 @@ -'use strict'; -var fs = require('graceful-fs'); -var path = require('path'); -var test = require('tap').test; -var rimraf = require('rimraf'); -var writeStream = require('../index.js'); - -var target = path.resolve(__dirname, 'test-chown'); - -test('chown works', function (t) { - t.plan(1); - var stream = writeStream(target, { - chown: {uid: process.getuid(), gid: process.getgid()}, - }); - var hadError = false; - stream.on('error', function (er) { - hadError = true; - console.log('#', er); - }); - stream.on('close', function () { - t.is(hadError, false, 'no errors before close'); - }); - stream.end(); -}); - -test('chown fails', function (t) { - t.plan(1); - fs.chown = function (file, uid, gid, cb) { - cb(new Error('TEST BREAK')); - }; - var stream = writeStream(target, { - chown: {uid: process.getuid(), gid: process.getgid()}, - }); - var hadError = false; - stream.on('error', function (er) { - hadError = true; - console.log('#', er); - }); - stream.on('close', function () { - t.is(hadError, true, 'error before close'); - }); - stream.end(); -}); - -test('cleanup', function (t) { - rimraf.sync(target); - t.end(); -}); diff --git a/packages/utils/fs-write-stream-atomic/test/rename-eperm.js b/packages/utils/fs-write-stream-atomic/test/rename-eperm.js deleted file mode 100644 index 32ce1f6933c..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/rename-eperm.js +++ /dev/null @@ -1,154 +0,0 @@ -'use strict'; -var fs = require('graceful-fs'); -var path = require('path'); -var test = require('tap').test; -var rimraf = require('rimraf'); -var writeStream = require('../index.js'); - -var target = path.resolve(__dirname, 'test-rename-eperm1'); -var target2 = path.resolve(__dirname, 'test-rename-eperm2'); -var target3 = path.resolve(__dirname, 'test-rename-eperm3'); - -test('rename eperm none existing file', function (t) { - t.plan(2); - - var _rename = fs.rename; - fs.existsSync = function (src) { - return true; - }; - fs.rename = function (src, dest, cb) { - // simulate a failure during rename where the file - // is renamed successfully but the process encounters - // an EPERM error and the target file does not exist - _rename(src, dest, function (e) { - var err = new Error('TEST BREAK'); - err.syscall = 'rename'; - err.code = 'EPERM'; - cb(err); - }); - }; - - var stream = writeStream(target, {isWin: true}); - var hadError = false; - var calledFinish = false; - stream.on('error', function (er) { - hadError = true; - console.log('#', er); - }); - stream.on('finish', function () { - calledFinish = true; - }); - stream.on('close', function () { - t.is(hadError, true, 'error was caught'); - t.is(calledFinish, false, 'finish was called before close'); - }); - stream.end(); -}); - -// test existing file with diff. content -test('rename eperm existing file different content', function (t) { - t.plan(2); - - var _rename = fs.rename; - fs.existsSync = function (src) { - return true; - }; - fs.rename = function (src, dest, cb) { - // simulate a failure during rename where the file - // is renamed successfully but the process encounters - // an EPERM error and the target file that has another content than the - // destination - _rename(src, dest, function (e) { - fs.writeFile(src, 'dest', function (writeErr) { - if (writeErr) { - return console.log('WRITEERR: ' + writeErr); - } - - fs.writeFile(target2, 'target', function (writeErr) { - if (writeErr) { - return console.log('WRITEERR: ' + writeErr); - } - - var err = new Error('TEST BREAK'); - err.syscall = 'rename'; - err.code = 'EPERM'; - cb(err); - }); - }); - }); - }; - - var stream = writeStream(target2, {isWin: true}); - var hadError = false; - var calledFinish = false; - stream.on('error', function (er) { - hadError = true; - console.log('#', er); - }); - stream.on('finish', function () { - calledFinish = true; - }); - stream.on('close', function () { - t.is(hadError, true, 'error was caught'); - t.is(calledFinish, false, 'finish was called before close'); - }); - stream.end(); -}); - -// test existing file with the same content -// test existing file with diff. content -test('rename eperm existing file different content', function (t) { - t.plan(2); - - var _rename = fs.rename; - fs.existsSync = function (src) { - return true; - }; - fs.rename = function (src, dest, cb) { - // simulate a failure during rename where the file - // is renamed successfully but the process encounters - // an EPERM error and the target file that has the same content than the - // destination - _rename(src, dest, function (e) { - fs.writeFile(src, 'target2', function (writeErr) { - if (writeErr) { - return console.log('WRITEERR: ' + writeErr); - } - - fs.writeFile(target3, 'target2', function (writeErr) { - if (writeErr) { - return console.log('WRITEERR: ' + writeErr); - } - - var err = new Error('TEST BREAK'); - err.syscall = 'rename'; - err.code = 'EPERM'; - cb(err); - }); - }); - }); - }; - - var stream = writeStream(target3, {isWin: true}); - var hadError = false; - var calledFinish = false; - stream.on('error', function (er) { - hadError = true; - console.log('#', er); - }); - stream.on('finish', function () { - calledFinish = true; - }); - stream.on('close', function () { - t.is(hadError, false, 'error was caught'); - t.is(calledFinish, true, 'finish was called before close'); - }); - stream.end(); -}); - -test('cleanup', function (t) { - rimraf.sync(target); - rimraf.sync(target2); - rimraf.sync(target3); - t.end(); -}); diff --git a/packages/utils/fs-write-stream-atomic/test/rename-fail.js b/packages/utils/fs-write-stream-atomic/test/rename-fail.js deleted file mode 100644 index f4a4c0af6cc..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/rename-fail.js +++ /dev/null @@ -1,30 +0,0 @@ -'use strict'; -var fs = require('graceful-fs'); -var path = require('path'); -var test = require('tap').test; -var rimraf = require('rimraf'); -var writeStream = require('../index.js'); - -var target = path.resolve(__dirname, 'test-rename'); - -test('rename fails', function (t) { - t.plan(1); - fs.rename = function (src, dest, cb) { - cb(new Error('TEST BREAK')); - }; - var stream = writeStream(target); - var hadError = false; - stream.on('error', function (er) { - hadError = true; - console.log('#', er); - }); - stream.on('close', function () { - t.is(hadError, true, 'error before close'); - }); - stream.end(); -}); - -test('cleanup', function (t) { - rimraf.sync(target); - t.end(); -}); diff --git a/packages/utils/fs-write-stream-atomic/test/slow-close.js b/packages/utils/fs-write-stream-atomic/test/slow-close.js deleted file mode 100644 index ec9e0832def..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/slow-close.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; -var fs = require('graceful-fs'); -var path = require('path'); -var test = require('tap').test; -var rimraf = require('rimraf'); -var writeStream = require('../index.js'); - -var target = path.resolve(__dirname, 'test-chown'); - -test('slow close', function (t) { - t.plan(2); - // The goal here is to simulate the "file close" step happening so slowly - // that the whole close/rename process could finish before the file is - // actually closed (and thus buffers truely flushed to the OS). In - // previous versions of this module, this would result in the module - // emitting finish & close before the file was fully written and in - // turn, could break other layers that tried to read the new file. - var realEmit = fs.WriteStream.prototype.emit; - var reallyClosed = false; - fs.WriteStream.prototype.emit = function (event) { - if (event !== 'close') return realEmit.apply(this, arguments); - setTimeout( - function () { - reallyClosed = true; - realEmit.call(this, 'close'); - }.bind(this), - 200, - ); - }; - var stream = writeStream(target); - stream.on('finish', function () { - t.is(reallyClosed, true, "didn't finish before target was closed"); - }); - stream.on('close', function () { - t.is(reallyClosed, true, "didn't close before target was closed"); - }); - stream.end(); -}); - -test('cleanup', function (t) { - rimraf.sync(target); - t.end(); -}); diff --git a/packages/utils/fs-write-stream-atomic/test/thread-id.js b/packages/utils/fs-write-stream-atomic/test/thread-id.js deleted file mode 100644 index 850fafe770d..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/thread-id.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict'; - -var test = require('tap').test; -var path = require('path'); -var threadId = require('../thread-id'); - -var Worker; -try { - Worker = require('worker_threads').Worker; -} catch (e) {} - -test('the main process has thread -1', function (t) { - t.equal(threadId, -1); - t.end(); -}); - -if (Worker != null) { - test('workers have positive integer threadIds', function (t) { - t.plan(2); - - var w1 = new Worker( - path.join(__dirname, '../fixtures/thread-id-test-worker.js'), - ); - w1.once('message', function (message) { - t.equal(message, 1); - }); - var w2 = new Worker( - path.join(__dirname, '../fixtures/thread-id-test-worker.js'), - ); - w2.once('message', function (message) { - t.equal(message, 2); - }); - }); -} diff --git a/packages/utils/fs-write-stream-atomic/test/toolong.js b/packages/utils/fs-write-stream-atomic/test/toolong.js deleted file mode 100644 index 40cf5e8a435..00000000000 --- a/packages/utils/fs-write-stream-atomic/test/toolong.js +++ /dev/null @@ -1,33 +0,0 @@ -var path = require('path'); -var test = require('tap').test; -var writeStream = require('../index.js'); - -function repeat(times, string) { - var output = ''; - for (var ii = 0; ii < times; ++ii) { - output += string; - } - return output; -} - -var target = path.resolve(__dirname, repeat(1000, 'test')); - -test('name too long', function (t) { - t.plan(2); - var stream = writeStream(target); - var hadError = false; - stream.on('error', function (er) { - if (!hadError) { - t.is( - er.code, - 'ENAMETOOLONG', - target.length + ' character name results in ENAMETOOLONG', - ); - hadError = true; - } - }); - stream.on('close', function () { - t.ok(hadError, 'got error before close'); - }); - stream.end(); -}); diff --git a/packages/utils/fs-write-stream-atomic/thread-id.js b/packages/utils/fs-write-stream-atomic/thread-id.js deleted file mode 100644 index 51397c54ceb..00000000000 --- a/packages/utils/fs-write-stream-atomic/thread-id.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; - -var threadId; -try { - var Worker = require('worker_threads'); - if (Worker.isMainThread) { - threadId = -1; - } else { - threadId = Worker.threadId; - } -} catch (e) { - // no worker support - threadId = -1; -} - -module.exports = threadId; diff --git a/yarn.lock b/yarn.lock index a9ba253834f..8328c591f81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3580,9 +3580,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001272, caniuse-lite@^1.0.30001286: - version "1.0.30001286" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz#3e9debad420419618cfdf52dc9b6572b28a8fff6" - integrity sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ== + version "1.0.30001349" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001349.tgz#90740086a2eb2e825084944169d313c9793aeba4" + integrity sha512-VFaWW3jeo6DLU5rwdiasosxhYSduJgSGil4cSyX3/85fbctlE58pXAkWyuRmVA0r2RxsOSVYUTZcySJ8WpbTxw== caseless@~0.12.0: version "0.12.0"