[Pkg-javascript-commits] [node-cross-spawn-async] 01/07: Imported Upstream version 2.0.0
Ross Gammon
ross-guest at moszumanska.debian.org
Wed Sep 23 20:03:16 UTC 2015
This is an automated email from the git hooks/post-receive script.
ross-guest pushed a commit to branch master
in repository node-cross-spawn-async.
commit 73c18a6403ba70751c8bd093b9c2515be179897b
Author: Ross Gammon <rossgammon at mail.dk>
Date: Wed Sep 9 23:18:59 2015 +0200
Imported Upstream version 2.0.0
---
.editorconfig | 15 ++
.gitignore | 4 +
.jshintrc | 62 ++++++
.npmignore | 3 +
.travis.yml | 4 +
LICENSE | 19 ++
README.md | 42 ++++
appveyor.yml | 28 +++
index.js | 23 +++
lib/enoent.js | 51 +++++
lib/parse.js | 129 ++++++++++++
lib/resolveCommand.js | 31 +++
lib/util/mixIn.js | 15 ++
package.json | 41 ++++
test/fixtures/()%!^&;, .bat | 2 +
test/fixtures/bar space | 3 +
test/fixtures/bar space.bat | 2 +
test/fixtures/echo.js | 5 +
test/fixtures/exit.js | 1 +
test/fixtures/exit1 | 3 +
test/fixtures/exit1.bat | 1 +
test/fixtures/foo | 3 +
test/fixtures/foo.bat | 2 +
test/fixtures/prepare_()%!^&;, .sh | 3 +
test/fixtures/shebang | 3 +
test/fixtures/shebang_enoent | 3 +
test/prepare.js | 14 ++
test/test.js | 403 +++++++++++++++++++++++++++++++++++++
test/util/buffered.js | 33 +++
29 files changed, 948 insertions(+)
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..fe988fc
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,15 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[.gitignore]
+trim_trailing_whitespace = false
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c7fd650
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+node_modules/
+npm-debug.*
+test/fixtures/(*
+test/fixtures/shebang_noenv
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..5595ed2
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,62 @@
+{
+ "predef": [
+ "console",
+ "describe",
+ "it",
+ "after",
+ "afterEach",
+ "before",
+ "beforeEach"
+ ],
+
+ "indent": 4,
+ "node": true,
+ "devel": true,
+
+ "bitwise": false,
+ "curly": false,
+ "eqeqeq": true,
+ "forin": false,
+ "immed": true,
+ "latedef": false,
+ "newcap": true,
+ "noarg": true,
+ "noempty": false,
+ "nonew": true,
+ "plusplus": false,
+ "regexp": false,
+ "undef": true,
+ "unused": "vars",
+ "quotmark": "single",
+ "strict": false,
+ "trailing": true,
+ "camelcase": true,
+
+ "asi": false,
+ "boss": true,
+ "debug": false,
+ "eqnull": true,
+ "es5": false,
+ "esnext": false,
+ "evil": false,
+ "expr": true,
+ "funcscope": false,
+ "globalstrict": false,
+ "iterator": false,
+ "lastsemic": false,
+ "laxbreak": true,
+ "laxcomma": false,
+ "loopfunc": true,
+ "multistr": false,
+ "onecase": true,
+ "regexdash": false,
+ "scripturl": false,
+ "smarttabs": false,
+ "shadow": false,
+ "sub": false,
+ "supernew": true,
+ "validthis": false,
+
+ "nomen": false,
+ "white": true
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..93f2f73
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+node_modules/
+npm-debug.*
+test/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4feb925
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,4 @@
+language: node_js
+node_js:
+ - '0.10'
+ - '0.12'
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e898822
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2015 IndigoUnited
+
+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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8e96bda
--- /dev/null
+++ b/README.md
@@ -0,0 +1,42 @@
+# cross-spawn-async [![Build Status](https://travis-ci.org/IndigoUnited/node-cross-spawn-async.svg?branch=master)](https://travis-ci.org/IndigoUnited/node-cross-spawn-async) [![Build status](https://ci.appveyor.com/api/projects/status/9rglfmcmxuu5lbcq/branch/master?svg=true)](https://ci.appveyor.com/project/satazor/node-cross-spawn-async/branch/master)
+
+A cross platform solution to node's spawn.
+
+
+## Installation
+
+`$ npm install cross-spawn-async`
+
+
+## Why
+
+Node has issues when using spawn on Windows:
+
+- It ignores [PATHEXT](https://github.com/joyent/node/issues/2318)
+- It does not support [shebangs](http://pt.wikipedia.org/wiki/Shebang)
+- It does not allow you to run `del` or `dir`
+- It does not properly escape arguments with spaces or special characters
+
+All these issues are handled correctly by `cross-spawn-async`.
+There are some known modules, such as [win-spawn](https://github.com/ForbesLindesay/win-spawn), that try to solve this but they are either broken or provide faulty escaping of shell arguments.
+
+
+## Usage
+
+Exactly the same way as node's [`spawn`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options), so it's a drop in replacement.
+
+```javascript
+var spawn = require('cross-spawn-async');
+
+var process = spawn('npm', ['list', '-g', '-depth' '0'], { stdio: 'inherit' });
+```
+
+
+## Tests
+
+`$ npm test`
+
+
+## License
+
+Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..22c6f43
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,28 @@
+# appveyor file
+# http://www.appveyor.com/docs/appveyor-yml
+
+# build version format
+version: "{build}"
+
+# fix lineendings in Windows
+init:
+ - git config --global core.autocrlf input
+
+# what combinations to test
+environment:
+ matrix:
+ - nodejs_version: 0.10
+ - nodejs_version: 0.12
+
+# get the latest stable version of Node 0.STABLE.latest
+install:
+ - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
+ - npm install
+
+build: off
+
+test_script:
+ - node --version
+ - npm --version
+ - ps: npm test --no-color # PowerShell
+ - cmd: npm test --no-color
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..c8d678d
--- /dev/null
+++ b/index.js
@@ -0,0 +1,23 @@
+var cp = require('child_process');
+var parse = require('./lib/parse');
+var enoent = require('./lib/enoent');
+
+function spawn(command, args, options) {
+ var parsed;
+ var spawned;
+
+ // Parse the arguments
+ parsed = parse(command, args, options);
+
+ // Spawn the child process
+ spawned = cp.spawn(parsed.command, parsed.args, parsed.options);
+
+ // Hook into child process "exit" event to emit an error if the command
+ // does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16
+ enoent.hookChildProcess(spawned, parsed);
+
+ return spawned;
+}
+
+module.exports = spawn;
+module.exports.spawn = spawn;
diff --git a/lib/enoent.js b/lib/enoent.js
new file mode 100644
index 0000000..e09d7f5
--- /dev/null
+++ b/lib/enoent.js
@@ -0,0 +1,51 @@
+'use strict';
+
+var isWin = process.platform === 'win32';
+
+function notFoundError(command, syscall) {
+ var err;
+
+ err = new Error(syscall + ' ' + command + ' ENOENT');
+ err.code = err.errno = 'ENOENT';
+ err.syscall = syscall + ' ' + command;
+
+ return err;
+}
+
+function hookChildProcess(cp, parsed) {
+ var originalEmit;
+
+ if (!isWin) {
+ return;
+ }
+
+ originalEmit = cp.emit;
+ cp.emit = function (name, arg1) {
+ var err;
+
+ // If emitting "exit" event and exit code is 1, we need to check if
+ // the command exists and emit an "error" instead
+ // See: https://github.com/IndigoUnited/node-cross-spawn/issues/16
+ if (name === 'exit') {
+ err = verifyENOENT(arg1, parsed, 'spawn');
+
+ if (err) {
+ return originalEmit.call(cp, 'error', err);
+ }
+ }
+
+ return originalEmit.apply(cp, arguments);
+ };
+}
+
+function verifyENOENT(status, parsed, syscall) {
+ if (isWin && status === 1 && !parsed.file) {
+ return notFoundError(parsed.original, syscall);
+ }
+
+ return null;
+}
+
+module.exports.hookChildProcess = hookChildProcess;
+module.exports.verifyENOENT = verifyENOENT;
+module.exports.notFoundError = notFoundError;
diff --git a/lib/parse.js b/lib/parse.js
new file mode 100644
index 0000000..410c6e3
--- /dev/null
+++ b/lib/parse.js
@@ -0,0 +1,129 @@
+var fs = require('fs');
+var LRU = require('lru-cache');
+var resolveCommand = require('./resolveCommand');
+var mixIn = require('./util/mixIn');
+
+var isWin = process.platform === 'win32';
+var shebangCache = LRU({ max: 50, maxAge: 30 * 1000 }); // Cache just for 30sec
+
+function readShebang(command) {
+ var buffer;
+ var fd;
+ var match;
+ var shebang;
+
+ // Check if it is in the cache first
+ if (shebangCache.has(command)) {
+ return shebangCache.get(command);
+ }
+
+ // Read the first 150 bytes from the file
+ buffer = new Buffer(150);
+
+ try {
+ fd = fs.openSync(command, 'r');
+ fs.readSync(fd, buffer, 0, 150, 0);
+ } catch (e) {}
+
+ // Check if it is a shebang
+ match = buffer.toString().trim().match(/\#\!(.+)/i);
+
+ if (match) {
+ shebang = match[1].replace(/\/usr\/bin\/env\s+/i, ''); // Remove /usr/bin/env
+ }
+
+ // Store the shebang in the cache
+ shebangCache.set(command, shebang);
+
+ return shebang;
+}
+
+function escapeArg(arg, quote) {
+ // Convert to string
+ arg = '' + arg;
+
+ // If we are not going to quote the argument,
+ // escape shell metacharacters, including double and single quotes:
+ if (!quote) {
+ arg = arg.replace(/([\(\)%!\^<>&|;,"' ])/g, '^$1');
+ } else {
+ // Sequence of backslashes followed by a double quote:
+ // double up all the backslashes and escape the double quote
+ arg = arg.replace(/(\\*)"/gi, '$1$1\\"');
+
+ // Sequence of backslashes followed by the end of the string
+ // (which will become a double quote later):
+ // double up all the backslashes
+ arg = arg.replace(/(\\*)$/, '$1$1');
+
+ // All other backslashes occur literally
+
+ // Quote the whole thing:
+ arg = '"' + arg + '"';
+ }
+
+ return arg;
+}
+
+function escapeCommand(command) {
+ // Do not escape if this command is not dangerous..
+ // We do this so that commands like "echo" or "ifconfig" work
+ // Quoting them, will make them unnaccessible
+ return /^[a-z0-9_-]+$/i.test(command) ? command : escapeArg(command, true);
+}
+
+function parseCall(command, args, options) {
+ var shebang;
+ var applyQuotes;
+ var file;
+ var original;
+
+ // Normalize arguments, similar to nodejs
+ if (args && !Array.isArray(args)) {
+ options = args;
+ args = null;
+ }
+
+ args = args ? args.slice(0) : []; // Clone array to avoid changing the original
+ options = mixIn({}, options);
+ original = command;
+
+ if (isWin) {
+ // Detect & add support for shebangs
+ // Note that if we were able to resolve the command, we skip this step entirely
+ file = resolveCommand(command);
+
+ if (!file) {
+ file = resolveCommand(command, true);
+ shebang = file && readShebang(file);
+ if (shebang) {
+ args.unshift(file);
+ command = shebang;
+ }
+ }
+
+ // Escape command & arguments
+ applyQuotes = command !== 'echo'; // Do not quote arguments for the special "echo" command
+ command = escapeCommand(command);
+ args = args.map(function (arg) {
+ return escapeArg(arg, applyQuotes);
+ });
+
+ // Use cmd.exe
+ args = ['/s', '/c', '"' + command + (args.length ? ' ' + args.join(' ') : '') + '"'];
+ command = process.env.comspec || 'cmd.exe';
+
+ // Tell node's spawn that the arguments are already escaped
+ options.windowsVerbatimArguments = true;
+ }
+
+ return {
+ command: command,
+ args: args,
+ options: options,
+ file: file,
+ original: original
+ };
+}
+
+module.exports = parseCall;
diff --git a/lib/resolveCommand.js b/lib/resolveCommand.js
new file mode 100644
index 0000000..943d895
--- /dev/null
+++ b/lib/resolveCommand.js
@@ -0,0 +1,31 @@
+'use strict';
+
+var path = require('path');
+var which = require('which');
+var LRU = require('lru-cache');
+
+var resolveCache = LRU({ max: 50, maxAge: 30 * 1000 }); // Cache just for 30sec
+
+function resolveCommand(command, noExtension) {
+ var resolved;
+
+ noExtension = !!noExtension;
+ resolved = resolveCache.get(command + '!' + noExtension);
+
+ // Check if its resolved in the cache
+ if (resolveCache.has(command)) {
+ return resolveCache.get(command);
+ }
+
+ try {
+ resolved = !noExtension ?
+ which.sync(command) :
+ which.sync(command, { pathExt: path.delimiter + (process.env.PATHEXT || '') });
+ } catch (e) {}
+
+ resolveCache.set(command + '!' + noExtension, resolved);
+
+ return resolved;
+}
+
+module.exports = resolveCommand;
diff --git a/lib/util/mixIn.js b/lib/util/mixIn.js
new file mode 100644
index 0000000..9483f8b
--- /dev/null
+++ b/lib/util/mixIn.js
@@ -0,0 +1,15 @@
+'use strict';
+
+function mixIn(target, source) {
+ var key;
+
+ // No need to check for hasOwnProperty.. this is used
+ // just in plain objects
+ for (key in source) {
+ target[key] = source[key];
+ }
+
+ return target;
+}
+
+module.exports = mixIn;
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f56cf7e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "cross-spawn-async",
+ "version": "2.0.0",
+ "description": "Cross platform child_process#spawn",
+ "main": "index.js",
+ "scripts": {
+ "test": "node test/prepare && mocha --bail -R spec test/test"
+ },
+ "bugs": {
+ "url": "https://github.com/IndigoUnited/node-cross-spawn-async/issues/"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/IndigoUnited/node-cross-spawn-async.git"
+ },
+ "keywords": [
+ "spawn",
+ "windows",
+ "cross",
+ "platform",
+ "path",
+ "ext",
+ "path-ext",
+ "path_ext",
+ "shebang",
+ "hashbang",
+ "cmd",
+ "execute"
+ ],
+ "author": "IndigoUnited <hello at indigounited.com> (http://indigounited.com)",
+ "license": "MIT",
+ "dependencies": {
+ "lru-cache": "^2.6.5",
+ "which": "^1.1.1"
+ },
+ "devDependencies": {
+ "expect.js": "^0.3.0",
+ "glob": "^5.0.12",
+ "mocha": "^2.2.5"
+ }
+}
diff --git a/test/fixtures/()%!^&;, .bat b/test/fixtures/()%!^&;, .bat
new file mode 100755
index 0000000..f248e1f
--- /dev/null
+++ b/test/fixtures/()%!^&;, .bat
@@ -0,0 +1,2 @@
+ at echo off
+echo special
diff --git a/test/fixtures/bar space b/test/fixtures/bar space
new file mode 100755
index 0000000..b492ccc
--- /dev/null
+++ b/test/fixtures/bar space
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo bar
diff --git a/test/fixtures/bar space.bat b/test/fixtures/bar space.bat
new file mode 100755
index 0000000..96332af
--- /dev/null
+++ b/test/fixtures/bar space.bat
@@ -0,0 +1,2 @@
+ at echo off
+echo bar
diff --git a/test/fixtures/echo.js b/test/fixtures/echo.js
new file mode 100755
index 0000000..430da7a
--- /dev/null
+++ b/test/fixtures/echo.js
@@ -0,0 +1,5 @@
+var args = process.argv.slice(2);
+
+args.forEach(function (arg, index) {
+ process.stdout.write(arg + (index < args.length - 1 ? '\n' : ''));
+});
diff --git a/test/fixtures/exit.js b/test/fixtures/exit.js
new file mode 100644
index 0000000..ea1a66f
--- /dev/null
+++ b/test/fixtures/exit.js
@@ -0,0 +1 @@
+process.exit(25);
diff --git a/test/fixtures/exit1 b/test/fixtures/exit1
new file mode 100755
index 0000000..d87f29e
--- /dev/null
+++ b/test/fixtures/exit1
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+exit 1
diff --git a/test/fixtures/exit1.bat b/test/fixtures/exit1.bat
new file mode 100755
index 0000000..379a4c9
--- /dev/null
+++ b/test/fixtures/exit1.bat
@@ -0,0 +1 @@
+exit 1
diff --git a/test/fixtures/foo b/test/fixtures/foo
new file mode 100755
index 0000000..17f236c
--- /dev/null
+++ b/test/fixtures/foo
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo foo
diff --git a/test/fixtures/foo.bat b/test/fixtures/foo.bat
new file mode 100755
index 0000000..1d98135
--- /dev/null
+++ b/test/fixtures/foo.bat
@@ -0,0 +1,2 @@
+ at echo off
+echo foo
diff --git a/test/fixtures/prepare_()%!^&;, .sh b/test/fixtures/prepare_()%!^&;, .sh
new file mode 100755
index 0000000..051009c
--- /dev/null
+++ b/test/fixtures/prepare_()%!^&;, .sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo special
diff --git a/test/fixtures/shebang b/test/fixtures/shebang
new file mode 100755
index 0000000..5650cbd
--- /dev/null
+++ b/test/fixtures/shebang
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+process.stdout.write('shebang works!');
diff --git a/test/fixtures/shebang_enoent b/test/fixtures/shebang_enoent
new file mode 100755
index 0000000..bd86265
--- /dev/null
+++ b/test/fixtures/shebang_enoent
@@ -0,0 +1,3 @@
+#!/usr/bin/env somecommandthatwillneverexist
+
+echo foo
diff --git a/test/prepare.js b/test/prepare.js
new file mode 100644
index 0000000..addb078
--- /dev/null
+++ b/test/prepare.js
@@ -0,0 +1,14 @@
+var glob = require('glob');
+var fs = require('fs');
+
+var fixturesDir = __dirname + '/fixtures';
+
+glob.sync('prepare_*', { cwd: __dirname + '/fixtures' }).forEach(function (file) {
+ var contents = fs.readFileSync(fixturesDir + '/' + file);
+ var finalFile = file.replace(/^prepare_/, '').replace(/\.sh$/, '');
+
+ fs.writeFileSync(fixturesDir + '/' + finalFile, contents);
+ fs.chmodSync(fixturesDir + '/' + finalFile, 0777);
+
+ process.stdout.write('Copied "' + file + '" to "' + finalFile + '"\n');
+});
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..083a95c
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,403 @@
+'use strict';
+
+var path = require('path');
+var fs = require('fs');
+var which = require('which');
+var expect = require('expect.js');
+var buffered = require('./util/buffered');
+var spawn = require('../');
+
+var isWin = process.platform === 'win32';
+
+// Fix AppVeyor tests because Git bin folder is in PATH and it has a "echo" program there
+if (isWin) {
+ process.env.PATH = process.env.PATH
+ .split(path.delimiter)
+ .filter(function (entry) {
+ return !/\\git\\bin$/i.test(path.normalize(entry));
+ })
+ .join(path.delimiter);
+}
+
+describe('cross-spawn', function () {
+ it('should support shebang in executables (with /usr/bin/env)', function (next) {
+ buffered(__dirname + '/fixtures/shebang', function (err, data, code) {
+ var envPath;
+
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('shebang works!');
+
+ // Test if the actual shebang file is resolved against the PATH
+ envPath = process.env.PATH;
+ process.env.PATH = path.normalize(__dirname + '/fixtures/') + path.delimiter + process.env.PATH;
+
+ buffered('shebang', function (err, data, code) {
+ process.env.PATH = envPath;
+
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('shebang works!');
+
+ next();
+ });
+ });
+ });
+
+ it('should support shebang in executables (without /usr/bin/env)', function (next) {
+ var nodejs = which.sync('node');
+ var file = __dirname + '/fixtures/shebang_noenv';
+
+ fs.writeFileSync(file, '#!' + nodejs + '\n\nprocess.stdout.write(\'shebang works!\');', {
+ mode: parseInt('0777', 8)
+ });
+
+ buffered(file, function (err, data, code) {
+ var envPath;
+
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('shebang works!');
+
+ // Test if the actual shebang file is resolved against the PATH
+ envPath = process.env.PATH;
+ process.env.PATH = path.normalize(__dirname + '/fixtures/') + path.delimiter + process.env.PATH;
+
+ buffered('shebang_noenv', function (err, data, code) {
+ process.env.PATH = envPath;
+
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('shebang works!');
+
+ next();
+ });
+ });
+ });
+
+ it('should expand using PATHEXT properly', function (next) {
+ buffered(__dirname + '/fixtures/foo', function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('foo');
+
+ next();
+ });
+ });
+
+ it('should handle commands with spaces', function (next) {
+ buffered(__dirname + '/fixtures/bar space', function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('bar');
+
+ next();
+ });
+ });
+
+ it('should handle commands with special shell chars', function (next) {
+ buffered(__dirname + '/fixtures/()%!^&;, ', function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('special');
+
+ next();
+ });
+ });
+
+ it('should handle empty arguments', function (next) {
+ buffered('node', [
+ __dirname + '/fixtures/echo',
+ 'foo',
+ '',
+ 'bar'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('foo\n\nbar');
+
+ buffered('echo', [
+ 'foo',
+ '',
+ 'bar'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('foo bar');
+
+ next();
+ });
+ });
+ });
+
+ it('should handle non-string arguments', function (next) {
+ buffered('node', [
+ __dirname + '/fixtures/echo',
+ 1234
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('1234');
+
+ next();
+ });
+ });
+
+ it('should handle arguments with spaces', function (next) {
+ buffered('node', [
+ __dirname + '/fixtures/echo',
+ 'I am',
+ 'André Cruz'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('I am\nAndré Cruz');
+
+ next();
+ });
+ });
+
+ it('should handle arguments with \\"', function (next) {
+ buffered('node', [
+ __dirname + '/fixtures/echo',
+ 'foo',
+ '\\"',
+ 'bar'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('foo\n\\"\nbar');
+
+ next();
+ });
+ });
+
+ it('should handle arguments that end with \\', function (next) {
+ buffered('node', [
+ __dirname + '/fixtures/echo',
+ 'foo',
+ 'bar\\',
+ 'baz'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('foo\nbar\\\nbaz');
+
+ next();
+ });
+ });
+
+ it('should handle arguments that contain shell special chars', function (next) {
+ buffered('node', [
+ __dirname + '/fixtures/echo',
+ 'foo',
+ '()',
+ 'foo',
+ '%!',
+ 'foo',
+ '^<',
+ 'foo',
+ '>&',
+ 'foo',
+ '|;',
+ 'foo',
+ ', ',
+ 'foo'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.equal('foo\n()\nfoo\n%!\nfoo\n^<\nfoo\n>&\nfoo\n|;\nfoo\n, \nfoo');
+
+ next();
+ });
+ });
+
+ it('should handle special arguments when using echo', function (next) {
+ buffered('echo', ['foo\\"foo\\foo&bar"foo\'bar'], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('foo\\"foo\\foo&bar"foo\'bar');
+
+ buffered('echo', [
+ 'foo',
+ '()',
+ 'foo',
+ '%!',
+ 'foo',
+ '^<',
+ 'foo',
+ '>&',
+ 'foo',
+ '|;',
+ 'foo',
+ ', ',
+ 'foo'
+ ], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('foo () foo %! foo ^< foo >& foo |; foo , foo');
+
+ next();
+ });
+ });
+ });
+
+ it('should handle optional args correctly', function (next) {
+ buffered(__dirname + '/fixtures/foo', function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+
+ buffered(__dirname + '/fixtures/foo', {
+ stdio: ['pipe', 'ignore', 'pipe'],
+ }, function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.be(null);
+
+ buffered(__dirname + '/fixtures/foo', null, {
+ stdio: ['pipe', 'ignore', 'pipe'],
+ }, function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data).to.be(null);
+
+ next();
+ });
+ });
+ });
+ });
+
+ it('should not mutate args nor options', function (next) {
+ var args = [];
+ var options = {};
+
+ buffered(__dirname + '/fixtures/foo', function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+
+ expect(args).to.have.length(0);
+ expect(Object.keys(options)).to.have.length(0);
+
+ next();
+ });
+ });
+
+ it('should give correct exit code', function (next) {
+ buffered('node', [__dirname + '/fixtures/exit'], function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(25);
+
+ next();
+ });
+ });
+
+ it('should work with a relative command', function (next) {
+ buffered(path.relative(process.cwd(), __dirname + '/fixtures/foo'), function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('foo');
+
+ if (!isWin) {
+ return next();
+ }
+
+ buffered(path.relative(process.cwd(), __dirname + '/fixtures/foo.bat'), function (err, data, code) {
+ expect(err).to.not.be.ok();
+ expect(code).to.be(0);
+ expect(data.trim()).to.equal('foo');
+
+ next();
+ });
+ });
+ });
+
+ it('should emit "error" and "close" if command does not exist', function (next) {
+ var spawned;
+ var errors = [];
+ var timeout;
+
+ this.timeout(5000);
+
+ spawned = spawn('somecommandthatwillneverexist')
+ .on('error', function (err) {
+ errors.push(err);
+ })
+ .on('exit', function () {
+ spawned.removeAllListeners();
+ clearTimeout(timeout);
+ next(new Error('Should not emit exit'));
+ })
+ .on('close', function (code, signal) {
+ expect(code).to.not.be(0);
+ expect(signal).to.be(null);
+
+ timeout = setTimeout(function () {
+ var err;
+
+ expect(errors).to.have.length(1);
+
+ err = errors[0];
+ expect(err).to.be.an(Error);
+ expect(err.message).to.contain('spawn');
+ expect(err.message).to.contain('ENOENT');
+ expect(err.message).to.not.contain('undefined');
+ expect(err.code).to.be('ENOENT');
+ expect(err.errno).to.be('ENOENT');
+ expect(err.syscall).to.contain('spawn');
+ expect(err.syscall).to.not.contain('undefined');
+
+ next();
+ }, 1000);
+ });
+ });
+
+ it('should NOT emit "error" if shebang command does not exist', function (next) {
+ var spawned;
+ var exited;
+ var timeout;
+
+ this.timeout(5000);
+
+ spawned = spawn(__dirname + '/fixtures/shebang_enoent')
+ .on('error', function (err) {
+ spawned.removeAllListeners();
+ clearTimeout(timeout);
+ next(new Error('Should not emit error'));
+ })
+ .on('exit', function () {
+ exited = true;
+ })
+ .on('close', function (code, signal) {
+ expect(code).to.not.be(0);
+ expect(signal).to.be(null);
+ expect(exited).to.be(true);
+
+ timeout = setTimeout(next, 1000);
+ });
+ });
+
+ it('should NOT emit "error" if the command actual exists but exited with 1', function (next) {
+ var spawned;
+ var exited;
+ var timeout;
+
+ this.timeout(5000);
+
+ spawned = spawn(__dirname + '/fixtures/exit1')
+ .on('error', function (err) {
+ spawned.removeAllListeners();
+ clearTimeout(timeout);
+ next(new Error('Should not emit error'));
+ })
+ .on('exit', function () {
+ exited = true;
+ })
+ .on('close', function (code, signal) {
+ expect(code).to.not.be(0);
+ expect(signal).to.be(null);
+ expect(exited).to.be(true);
+
+ timeout = setTimeout(next, 1000);
+ });
+ });
+});
diff --git a/test/util/buffered.js b/test/util/buffered.js
new file mode 100644
index 0000000..e9ce32d
--- /dev/null
+++ b/test/util/buffered.js
@@ -0,0 +1,33 @@
+'use strict';
+
+var spawn = require('../../');
+
+function buffered(command, args, options, callback) {
+ var cp;
+ var data = null;
+
+ if (typeof options === 'function') {
+ callback = options;
+ options = null;
+ }
+
+ if (typeof args === 'function') {
+ callback = args;
+ args = options = null;
+ }
+
+ cp = spawn(command, args, options);
+
+ cp.stdout && cp.stdout.on('data', function(buffer) {
+ data = data || '';
+ data += buffer.toString();
+ });
+
+ cp.on('error', callback);
+
+ cp.on('close', function(code) {
+ callback(null, data, code);
+ });
+}
+
+module.exports = buffered;
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-cross-spawn-async.git
More information about the Pkg-javascript-commits
mailing list