UNPKG

5.03 kBJavaScriptView Raw
1'use strict';
2
3const Binding = require('./binding');
4const FSError = require('./error');
5const FileSystem = require('./filesystem');
6const realBinding = process.binding('fs');
7const path = require('path');
8const fs = require('fs');
9
10const toNamespacedPath = FileSystem.toNamespacedPath;
11
12const realProcessProps = {
13 cwd: process.cwd,
14 chdir: process.chdir
15};
16const realCreateWriteStream = fs.createWriteStream;
17const realStats = realBinding.Stats;
18const realStatWatcher = realBinding.StatWatcher;
19
20/**
21 * Pre-patch fs binding.
22 * This allows mock-fs to work properly under nodejs v10+ readFile
23 * As ReadFileContext nodejs v10+ implementation traps original binding methods:
24 * const { FSReqWrap, close, read } = process.binding('fs');
25 * Note this patch only solves issue for readFile, as the require of
26 * ReadFileContext is delayed by readFile implementation.
27 * if (!ReadFileContext) ReadFileContext = require('internal/fs/read_file_context')
28 *
29 * @param {string} key Property name.
30 */
31function patch(key) {
32 const existingMethod = realBinding[key];
33 realBinding[key] = function() {
34 if (this._mockedBinding) {
35 return this._mockedBinding[key].apply(this, arguments);
36 } else {
37 return existingMethod.apply(this, arguments);
38 }
39 }.bind(realBinding);
40}
41
42for (const key in Binding.prototype) {
43 if (typeof realBinding[key] === 'function') {
44 // Stats and StatWatcher are constructors
45 if (key !== 'Stats' && key !== 'StatWatcher') {
46 patch(key);
47 }
48 }
49}
50
51function overrideBinding(binding) {
52 realBinding._mockedBinding = binding;
53
54 for (const key in binding) {
55 if (typeof realBinding[key] === 'function') {
56 // Stats and StatWatcher are constructors
57 if (key === 'Stats' || key === 'StatWatcher') {
58 realBinding[key] = binding[key];
59 }
60 } else if (typeof realBinding[key] === 'undefined') {
61 realBinding[key] = binding[key];
62 }
63 }
64}
65
66function overrideProcess(cwd, chdir) {
67 process.cwd = cwd;
68 process.chdir = chdir;
69}
70
71/**
72 * Have to disable write stream _writev on nodejs v10+.
73 *
74 * nodejs v8 lib/fs.js
75 * note binding.writeBuffers will use mock-fs patched writeBuffers.
76 *
77 * const binding = process.binding('fs');
78 * function writev(fd, chunks, position, callback) {
79 * // ...
80 * binding.writeBuffers(fd, chunks, position, req);
81 * }
82 *
83 * nodejs v10+ lib/internal/fs/streams.js
84 * note it uses original writeBuffers, bypassed mock-fs patched writeBuffers.
85 *
86 * const {writeBuffers} = internalBinding('fs');
87 * function writev(fd, chunks, position, callback) {
88 * // ...
89 * writeBuffers(fd, chunks, position, req);
90 * }
91 *
92 * Luckily _writev is an optional method on Writeable stream implementation.
93 * When _writev is missing, it will fall back to make multiple _write calls.
94 */
95function overrideCreateWriteStream() {
96 fs.createWriteStream = function(path, options) {
97 const output = realCreateWriteStream(path, options);
98 // disable _writev, this will over shadow WriteStream.prototype._writev
99 output._writev = undefined;
100 return output;
101 };
102}
103
104function restoreBinding() {
105 delete realBinding._mockedBinding;
106 realBinding.Stats = realStats;
107 realBinding.StatWatcher = realStatWatcher;
108}
109
110function restoreProcess() {
111 for (const key in realProcessProps) {
112 process[key] = realProcessProps[key];
113 }
114}
115
116function restoreCreateWriteStream() {
117 fs.createWriteStream = realCreateWriteStream;
118}
119
120/**
121 * Swap out the fs bindings for a mock file system.
122 * @param {Object} config Mock file system configuration.
123 * @param {Object} options Any filesystem options.
124 * @param {boolean} options.createCwd Create a directory for `process.cwd()`
125 * (defaults to `true`).
126 * @param {boolean} options.createTmp Create a directory for `os.tmpdir()`
127 * (defaults to `true`).
128 */
129exports = module.exports = function mock(config, options) {
130 const system = FileSystem.create(config, options);
131 const binding = new Binding(system);
132
133 overrideBinding(binding);
134
135 let currentPath = process.cwd();
136 overrideProcess(
137 function cwd() {
138 return currentPath;
139 },
140 function chdir(directory) {
141 if (!binding.stat(toNamespacedPath(directory)).isDirectory()) {
142 throw new FSError('ENOTDIR');
143 }
144 currentPath = path.resolve(currentPath, directory);
145 }
146 );
147
148 overrideCreateWriteStream();
149};
150
151/**
152 * Get hold of the mocked filesystem's 'root'
153 * If fs hasn't currently been replaced, this will return an empty object
154 */
155exports.getMockRoot = function() {
156 if (realBinding._mockedBinding) {
157 return realBinding.getSystem().getRoot();
158 } else {
159 return {};
160 }
161};
162
163/**
164 * Restore the fs bindings for the real file system.
165 */
166exports.restore = function() {
167 restoreBinding();
168 restoreProcess();
169 restoreCreateWriteStream();
170};
171
172/**
173 * Create a file factory.
174 */
175exports.file = FileSystem.file;
176
177/**
178 * Create a directory factory.
179 */
180exports.directory = FileSystem.directory;
181
182/**
183 * Create a symbolic link factory.
184 */
185exports.symlink = FileSystem.symlink;