From 015b22bcf3d0c2ff55e3bf11d464102247a9fcc2 Mon Sep 17 00:00:00 2001 From: Chunpeng Huo Date: Sat, 15 Aug 2020 14:16:21 +1000 Subject: [PATCH] fix: synchronize patching binding/createWriteStream/cwd/chdir All the patches now check same realBinding._mockedBinding. This should fix the edge cases around the new feature bypass(). follows up #306 --- lib/index.js | 19 ++++++-- test/lib/bypass.spec.js | 104 ++++++++++++++++++++++++++++++++++++++++ test/lib/index.spec.js | 74 ---------------------------- 3 files changed, 118 insertions(+), 79 deletions(-) create mode 100644 test/lib/bypass.spec.js diff --git a/lib/index.js b/lib/index.js index 2f6b5ac6..eae3868e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -87,7 +87,9 @@ function overrideCreateWriteStream() { fs.createWriteStream = function(path, options) { const output = realCreateWriteStream(path, options); // disable _writev, this will over shadow WriteStream.prototype._writev - output._writev = undefined; + if (realBinding._mockedBinding) { + output._writev = undefined; + } return output; }; } @@ -126,13 +128,20 @@ exports = module.exports = function mock(config, options) { let currentPath = process.cwd(); overrideProcess( function cwd() { - return currentPath; + if (realBinding._mockedBinding) { + return currentPath; + } + return realProcessProps.cwd(); }, function chdir(directory) { - if (!binding.stat(toNamespacedPath(directory)).isDirectory()) { - throw new FSError('ENOTDIR'); + if (realBinding._mockedBinding) { + if (!binding.stat(toNamespacedPath(directory)).isDirectory()) { + throw new FSError('ENOTDIR'); + } + currentPath = path.resolve(currentPath, directory); + } else { + return realProcessProps.chdir(directory); } - currentPath = path.resolve(currentPath, directory); } ); diff --git a/test/lib/bypass.spec.js b/test/lib/bypass.spec.js new file mode 100644 index 00000000..177cad66 --- /dev/null +++ b/test/lib/bypass.spec.js @@ -0,0 +1,104 @@ +'use strict'; + +const helper = require('../helper'); +const fs = require('fs'); +const mock = require('../../lib/index'); +const path = require('path'); +const withPromise = helper.withPromise; + +const assert = helper.assert; + +describe('mock.bypass()', () => { + afterEach(mock.restore); + + it('runs a synchronous function using the real filesystem', () => { + mock({'/path/to/file': 'content'}); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + assert.isNotOk(fs.existsSync(__filename)); + assert.isOk(mock.bypass(() => fs.existsSync(__filename))); + + assert.isNotOk(fs.existsSync(__filename)); + }); + + it('handles functions that throw', () => { + mock({'/path/to/file': 'content'}); + + const error = new Error('oops'); + + assert.throws(() => { + mock.bypass(() => { + assert.isFalse(fs.existsSync('/path/to/file')); + throw error; + }); + }, error); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + }); + + it('bypasses patched process.cwd() and process.chdir()', () => { + const originalCwd = process.cwd(); + mock({ + dir: {} + }); + + process.chdir('dir'); + assert.equal(process.cwd(), path.join(originalCwd, 'dir')); + + mock.bypass(() => { + assert.equal(process.cwd(), originalCwd); + process.chdir('lib'); + assert.equal(process.cwd(), path.join(originalCwd, 'lib')); + process.chdir('..'); + assert.equal(process.cwd(), originalCwd); + }); + assert.equal(process.cwd(), path.join(originalCwd, 'dir')); + mock.restore(); + + assert.equal(process.cwd(), originalCwd); + }); + + withPromise.it('runs an async function using the real filesystem', done => { + mock({'/path/to/file': 'content'}); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + assert.isFalse(fs.existsSync(__filename)); + + const promise = mock.bypass(() => fs.promises.stat(__filename)); + assert.instanceOf(promise, Promise); + + promise + .then(stat => { + assert.isTrue(stat.isFile()); + assert.isFalse(fs.existsSync(__filename)); + done(); + }) + .catch(done); + }); + + withPromise.it('handles promise rejection', done => { + mock({'/path/to/file': 'content'}); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + assert.isFalse(fs.existsSync(__filename)); + + const error = new Error('oops'); + + const promise = mock.bypass(() => { + assert.isTrue(fs.existsSync(__filename)); + return Promise.reject(error); + }); + assert.instanceOf(promise, Promise); + + promise + .then(() => { + done(new Error('expected rejection')); + }) + .catch(err => { + assert.equal(err, error); + + assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); + done(); + }); + }); +}); diff --git a/test/lib/index.spec.js b/test/lib/index.spec.js index 802e011d..8164419a 100644 --- a/test/lib/index.spec.js +++ b/test/lib/index.spec.js @@ -8,7 +8,6 @@ const path = require('path'); const File = require('../../lib/file'); const {fixWin32Permissions} = require('../../lib/item'); const Directory = require('../../lib/directory'); -const withPromise = helper.withPromise; const assert = helper.assert; const assetsPath = path.resolve(__dirname, '../assets'); @@ -185,79 +184,6 @@ describe('The API', function() { }); }); - describe(`mock.bypass()`, () => { - afterEach(mock.restore); - - it('runs a synchronous function using the real filesystem', () => { - mock({'/path/to/file': 'content'}); - - assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); - assert.isNotOk(fs.existsSync(__filename)); - assert.isOk(mock.bypass(() => fs.existsSync(__filename))); - - assert.isNotOk(fs.existsSync(__filename)); - }); - - it('handles functions that throw', () => { - mock({'/path/to/file': 'content'}); - - const error = new Error('oops'); - - assert.throws(() => { - mock.bypass(() => { - assert.isFalse(fs.existsSync('/path/to/file')); - throw error; - }); - }, error); - - assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); - }); - - withPromise.it('runs an async function using the real filesystem', done => { - mock({'/path/to/file': 'content'}); - - assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); - assert.isFalse(fs.existsSync(__filename)); - - const promise = mock.bypass(() => fs.promises.stat(__filename)); - assert.instanceOf(promise, Promise); - - promise - .then(stat => { - assert.isTrue(stat.isFile()); - assert.isFalse(fs.existsSync(__filename)); - done(); - }) - .catch(done); - }); - - withPromise.it('handles promise rejection', done => { - mock({'/path/to/file': 'content'}); - - assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); - assert.isFalse(fs.existsSync(__filename)); - - const error = new Error('oops'); - - const promise = mock.bypass(() => { - assert.isTrue(fs.existsSync(__filename)); - return Promise.reject(error); - }); - assert.instanceOf(promise, Promise); - - promise - .then(() => { - done(new Error('expected rejection')); - }) - .catch(err => { - assert.equal(err, error); - - assert.equal(fs.readFileSync('/path/to/file', 'utf8'), 'content'); - done(); - }); - }); - }); - describe(`mock.load()`, () => { const statsCompareKeys = [ 'birthtime',