diff --git a/benchmark/common.js b/benchmark/common.js index ff54f2ea495ef5..45bd2a435b13cf 100644 --- a/benchmark/common.js +++ b/benchmark/common.js @@ -131,6 +131,7 @@ Benchmark.prototype._run = function() { var argv = queue[i++]; if (!argv) return; + argv = process.execArgv.concat(argv); var child = spawn(node, argv, { stdio: 'inherit' }); child.on('close', function(code, signal) { if (code) diff --git a/benchmark/fs/stat.js b/benchmark/fs/stat.js new file mode 100644 index 00000000000000..2c602aea1346ef --- /dev/null +++ b/benchmark/fs/stat.js @@ -0,0 +1,88 @@ +// Call fs.stat over and over again really fast. +// Then see how many times it got called. +// Yes, this is a silly benchmark. Most benchmarks are silly. + +var path = require('path'); +var common = require('../common.js'); +var fs = require('fs'); + +var FILES = [ + require.resolve('../../lib/assert.js'), + require.resolve('../../lib/console.js'), + require.resolve('../../lib/fs.js') +]; + +var VARIANTS = { + promise: createPromiseBasedTest(fs.stat), + callback: createCallBackBasedTest(fs.stat), +}; + +var bench = common.createBenchmark(main, { + dur: [5], + concurrent: [1, 10, 100], + variant: Object.keys(VARIANTS) +}); + +function main(conf) { + var stat = VARIANTS[conf.variant]; + + if (stat == VARIANTS.promise && !process.promisifyCore) { + bench.start(); + bench.end(0); + return; + } + + var calls = 0; + bench.start(); + setTimeout(function() { + bench.end(calls); + }, +conf.dur * 1000); + + var cur = +conf.concurrent; + while (cur--) run(); + + function run() { + var p = stat(next); + if (p) p.then(next); + } + + function next() { + calls++; + run(); + } +} + +function createCallBackBasedTest(stat) { + return function runStatViaCallbacks(cb) { + stat(FILES[0], function(err, data) { + if (err) throw err; + second(data); + }); + + function second() { + stat(FILES[1], function(err, data) { + if (err) throw err; + third(data); + }); + } + + function third() { + stat(FILES[2], function(err, data) { + if (err) throw err; + cb(data); + }); + } + }; +} + +function createPromiseBasedTest(stat) { + return function runStatViaPromises() { + return stat(FILES[0]) + .then(function secondP(data) { + return stat(FILES[1]); + }) + .then(function thirdP(data) { + return stat(FILES[2]); + }); + } +} diff --git a/lib/fs.js b/lib/fs.js index aa7b51df407f30..9f6cd973282f61 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -88,6 +88,13 @@ function maybeCallback(cb) { return util.isFunction(cb) ? cb : rethrow(); } +function _maybeCallbackOrPromise(cb) { + return util.isFunction(cb) ? cb : createCallbackBackedByPromise(); +} + +var maybeCallbackOrPromise = process.promisifyCore ? + _maybeCallbackOrPromise : maybeCallback; + // Ensure that callbacks run in the global context. Only use this function // for callbacks that are passed to the binding layer, callbacks that are // invoked from JS already run in the proper scope. @@ -101,6 +108,32 @@ function makeCallback(cb) { }; } +function _makeCallbackOrPromise(cb) { + if (!util.isFunction(cb)) { + return createCallbackBackedByPromise(); + } + + return function() { + return cb.apply(null, arguments); + }; +} + +var makeCallbackOrPromise = process.promisifyCore ? + _makeCallbackOrPromise : makeCallback; + +function createCallbackBackedByPromise() { + var cb; + var promise = new Promise(function(resolve, reject) { + cb = function(err, data) { + if (err) reject(err); + else resolve(data); + }; + }); + cb.promise = promise; + return cb; +} + + function assertEncoding(encoding) { if (encoding && !Buffer.isEncoding(encoding)) { throw new Error('Unknown encoding: ' + encoding); @@ -241,7 +274,7 @@ fs.existsSync = function(path) { }; fs.readFile = function(path, options, callback_) { - var callback = maybeCallback(arguments[arguments.length - 1]); + var callback = maybeCallbackOrPromise(arguments[arguments.length - 1]); if (util.isFunction(options) || !options) { options = { encoding: null, flag: 'r' }; @@ -262,7 +295,10 @@ fs.readFile = function(path, options, callback_) { var fd; var flag = options.flag || 'r'; - fs.open(path, flag, 438 /*=0666*/, function(er, fd_) { + fs.open(path, flag, 438 /*=0666*/, afterOpen); + return callback.promise; + + function afterOpen(er, fd_) { if (er) return callback(er); fd = fd_; @@ -291,7 +327,7 @@ fs.readFile = function(path, options, callback_) { buffer = new Buffer(size); read(); }); - }); + } function read() { if (size === 0) { @@ -781,11 +817,12 @@ fs.lstat = function(path, callback) { }; fs.stat = function(path, callback) { - callback = makeCallback(callback); - if (!nullCheck(path, callback)) return; + callback = makeCallbackOrPromise(callback); + if (!nullCheck(path, callback)) return callback.promise; var req = new FSReqWrap(); req.oncomplete = callback; binding.stat(pathModule._makeLong(path), req); + return callback.promise; }; fs.fstatSync = function(fd) { diff --git a/src/node.cc b/src/node.cc index e2a506680b4c94..699ff0fe6f0098 100644 --- a/src/node.cc +++ b/src/node.cc @@ -126,6 +126,7 @@ static bool print_eval = false; static bool force_repl = false; static bool trace_deprecation = false; static bool throw_deprecation = false; +static bool promisify_core = false; static const char* eval_string = nullptr; static bool use_debug_agent = false; static bool debug_wait_connect = false; @@ -2682,6 +2683,11 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(process, "noDeprecation", True(env->isolate())); } + // --promisify-core + if (promisify_core) { + READONLY_PROPERTY(process, "promisifyCore", True(env->isolate())); + } + // --throw-deprecation if (throw_deprecation) { READONLY_PROPERTY(process, "throwDeprecation", True(env->isolate())); @@ -2900,6 +2906,7 @@ static void PrintHelp() { " --throw-deprecation throw an exception anytime a deprecated " "function is used\n" " --trace-deprecation show stack traces on deprecations\n" + " --promisify-core EXPERIMENTAL: promise-enabled core API\n" " --v8-options print v8 command line options\n" " --max-stack-size=val set max v8 stack size (bytes)\n" #if defined(NODE_HAVE_I18N_SUPPORT) @@ -3014,6 +3021,8 @@ static void ParseArgs(int* argc, trace_deprecation = true; } else if (strcmp(arg, "--throw-deprecation") == 0) { throw_deprecation = true; + } else if (strcmp(arg, "--promisify-core") == 0) { + promisify_core = true; } else if (strcmp(arg, "--v8-options") == 0) { new_v8_argv[new_v8_argc] = "--help"; new_v8_argc += 1; diff --git a/test/simple/test-fs-readfile-promise.js b/test/simple/test-fs-readfile-promise.js new file mode 100644 index 00000000000000..f29adf5cdaa785 --- /dev/null +++ b/test/simple/test-fs-readfile-promise.js @@ -0,0 +1,78 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var common = require('../common'); +var assert = require('assert'); +var path = require('path'); +var fs = require('fs'); +var got_error = false; +var success_count = 0; +var expected_count = 0; +var filename; + +if (!process.promisifyCore) { + console.log( + '// Skipping tests of promisified API, `--promisify-core` is needed'); + return; +} + +// happy path, all arguments +expected_count++; +fs.readFile(__filename) + .then(function(content) { + assert.ok(/happy path, all arguments/.test(content)); + success_count++; + }) + .catch(reportTestError); + +// empty file, optional arguments skipped +expected_count++; +filename = path.join(common.fixturesDir, 'empty.txt'); +fs.readFile(filename, 'utf-8') + .then(function(content) { + assert.strictEqual('', content); + success_count++; + }) + .catch(reportTestError); + +// file does not exist +expected_count++; +filename = path.join(common.fixturesDir, 'does_not_exist.txt'); +fs.readFile(filename) + .then( + function(content) { + throw new Error('readFile should have failed for unkown file'); + }, + function(err) { + assert.equal('ENOENT', err.code); + success_count++; + }) + .catch(reportTestError); + +process.on('exit', function() { + assert.equal(expected_count, success_count); + assert.equal(false, got_error); +}); + +function reportTestError(err) { + console.error('\nTEST FAILED\n', err.stack, '\n'); + got_error = true; +} diff --git a/test/simple/test-fs-stat.js b/test/simple/test-fs-stat.js index 8c5a9c64c1534b..fefec4a2c68c37 100644 --- a/test/simple/test-fs-stat.js +++ b/test/simple/test-fs-stat.js @@ -24,6 +24,7 @@ var assert = require('assert'); var fs = require('fs'); var got_error = false; var success_count = 0; +var expected_count = 5; fs.stat('.', function(err, stats) { if (err) { @@ -41,6 +42,32 @@ fs.stat('.', function(err, stats) { assert.ok(stats.hasOwnProperty('blocks')); }); +if (!process.promisifyCore) { + console.log( + '// Skipping tests of promisified API, `--promisify-core` is needed'); +} else { + expected_count++; + fs.stat('.') + .then(function(stats) { + assert.ok(stats.hasOwnProperty('blksize')); + assert.ok(stats.hasOwnProperty('blocks')); + success_count++; + }) + .catch(reportTestError); + + expected_count++; + fs.stat('./does-not-exist') + .then( + function(stats) { + throw new Error('stat should have failed for unkown file'); + }, + function(err) { + assert.equal('ENOENT', err.code); + success_count++; + }) + .catch(reportTestError); +} + fs.lstat('.', function(err, stats) { if (err) { got_error = true; @@ -122,7 +149,11 @@ fs.stat(__filename, function(err, s) { }); process.on('exit', function() { - assert.equal(5, success_count); + assert.equal(expected_count, success_count); assert.equal(false, got_error); }); +function reportTestError(err) { + console.error('\nTEST FAILED\n', err.stack, '\n'); + got_error = true; +} diff --git a/tools/closure_linter/closure_linter.egg-info/SOURCES.txt b/tools/closure_linter/closure_linter.egg-info/SOURCES.txt index b64d829f7e9aea..fd8217d714904c 100644 --- a/tools/closure_linter/closure_linter.egg-info/SOURCES.txt +++ b/tools/closure_linter/closure_linter.egg-info/SOURCES.txt @@ -1,4 +1,5 @@ README +setup.cfg setup.py closure_linter/__init__.py closure_linter/checker.py