Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

real fs called when require is in the trace #145

Open
Slayer95 opened this issue Jul 15, 2016 · 10 comments
Open

real fs called when require is in the trace #145

Slayer95 opened this issue Jul 15, 2016 · 10 comments

Comments

@Slayer95
Copy link

Slayer95 commented Jul 15, 2016

mock-fs.js

'use strict';

const fs = require('fs');
const path = require('path');
const mock = require('mock-fs');

mock({
    'mock-fs-child.js': fs.readFileSync(path.resolve(__dirname, 'mock-fs-child.js')),
});
require('./mock-fs-child');

mock-fs-child.js

'use strict';

const fs = require('fs');
const path = require('path');

fs.accessSync(path.resolve(__dirname, 'mock-fs.js'));

The accessSync call throws in [email protected], but succeeds despite the file being absent in the sandbox in [email protected].

Node v. 6.3.0

@Slayer95
Copy link
Author

Slayer95 commented Jul 15, 2016

I have also been successfully using the real fs module for require module loading up to and including [email protected], with a bit of boilerplate, shown as follows (MIT Licensed)

'use strict';

const fs = require('fs');
const path = require('path');
const Module = require('module');

const mock = require('mock-fs');

const fsSandbox = {
    'mock-fs-child.js': fs.readFileSync(path.resolve(__dirname, 'mock-fs-child.js')),
};
mock.currentSandbox = fsSandbox;

// Node's module loading system should be backed up by the real file system.
Module.__resolveFilename__ = Module._resolveFilename;
Module._resolveFilename = function (request, parent) {
    if (request === 'fs') return this.__resolveFilename__(request, parent);
    mock.restore();
    try {
        return this.__resolveFilename__(request, parent);
    } finally {
        mock(fsSandbox);
    }
};
for (let ext in Module._extensions) {
    let defaultLoader = Module._extensions[ext];
    Module._extensions[ext] = function (module, filename) {
        mock.restore();
        try {
            return defaultLoader(module, filename);
        } finally {
            mock(fsSandbox);
        }
    };
}
Module.prototype.__compile__ = Module.prototype._compile;
Module.prototype._compile = function (content, filename) {
    // Use the sandbox to evaluate the code in our modules.
    mock(fsSandbox);
    try {
        return this.__compile__(content, filename);
    } finally {
        mock.restore();
    }
};

// `watchFile` is unsupported and throws with mock-fs
Object.defineProperty(fs, 'watchFile', {
    get: () => (() => null),
    set: () => null,
});

mock(fsSandbox);
require('./mock-fs-child');

mock-fs-child.js

'use strict';

const fs = require('fs');
const path = require('path');

fs.accessSync(path.resolve(__dirname, 'mock-fs.js'));

Here, the accessSync correctly throws in [email protected] and [email protected], but incorrectly succeeds in [email protected], for Node v4 and v6.
I have also verified that it works in a few of versions of io.js.

If you'd be interested in including it in mock-fs core, note that it's untested in Node v0.12 and below. You'd also probably want to make Module._extensions a Proxy for CoffeeScript support.

@blond
Copy link
Contributor

blond commented Jul 17, 2016

The PR #139 contains breaking changes.

An example of a package in which the tests are broken after update mock-fs to 3.11.0:

This is not expected behavior for mock-fs. Because it is impossible to write integration tests with [email protected].

I think need to leave behavior from [email protected].

@tschaub
Copy link
Owner

tschaub commented Jul 18, 2016

Would it be possible for you to use proxyquire if you want to override the behavior of require?

@blond - I'd be curious to get some more detail on what functionality you were relying on. As far as I could tell, calling require() after setting up a mock with [email protected] was unreliable at best (it would use the mock filesystem only if corresponding files existed in the real filesystem).

@Slayer95
Copy link
Author

@tschaub, this issue happens without overriding require.

Test case in the OP.

@Slayer95
Copy link
Author

ping

@Slayer95
Copy link
Author

I will probably run a fork with this issue patched

Would you entertain a patch that uses a Proxy to fix the caveat mentioned in the commit message, @tschaub?

@alekbarszczewski
Copy link

What can I do to make it work with require('path/to/mocked/module')?

@vjpr
Copy link

vjpr commented May 20, 2017

My use case is that I want require to use the file system for everything except files in a certain directory, and relative requires from anywhere in the mocked directory (i.e. __dirname).

I'm thinking of overriding Module.prototype._compile and using a fs.readFileSync to grab the string contents of the required file myself which should use mock-fs.

@vjpr
Copy link

vjpr commented May 20, 2017

I think if you override Module.resolveFilename then it works. Reading the contents of the file from disk uses fs.readFileSync, whereas resolving the filename with Module._resolveFilename uses something else (process.binding('fs') maybe?) that fs-mock doesn't touch.

@airtonix
Copy link

Is this ticket affecting a scenario like so:

// tricky-tool.js
const vfs = require('vinyl-fs');
const nconf = require('nconf').argv();
const map = require('map-stream'):

const IS_MAIN = module.parent || module.parent.id == '.';


function magic (file, done) {
  file.contents = superAwesomeSecretSauce(file.contents);
  done(null, file);
}

module.exports = function go () {
  vfs.src(nconf.source)
    .pipe(map(magic))
    .pipe(vfs.dest(nconf.dest));
}

IS_MAIN && go();
// tricky-tool.spec.js
const mock = require('mock-fs');
const log = require('debug')('test/tricky-tool');
const tape = require('tape');

function beforeEach () {
    log('beforeEach');
    mock({
        'template' : {
            'one.js': 'module.exports = {}',
            'one.json.tmpl': `{
                "something": "foo/{{name}}"
            }`,
            'releaserc.tmpl': `[config]
    something = {{name}}
            `
        }
    });
};

tape('tool is well tricky.', function (test) {
    const doTricks = require('./tricky-tool');
    beforeEach();
    doTricks();
    afterEach();
    test.end();
});

function afterEach () { mock.restore(); }

Here in this case, it seems to have references to both the mock fs and the real fs:

TAP version 13
# converts filenames properly
beforeEach
Template.context 1.77 KB
events.js:183
      throw er; // Unhandled 'error' event
      ^

Error: ENOENT: no such file or directory, lstat '/opt/app/template/one.js'
  • Template.context 1.77 KB means it actually required the contents of the package.json on real disk.
  • lstat '/opt/app/template/one.js' means it started the vinyl stream with the mocked fs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants