Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions lib/util/escape.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const escape = require('./escape');

// Expected values follow the cmd.exe escaping rules documented at
// https://qntm.org/cmd and the meta-char list in
// http://www.robvanderwoude.com/escapechars.php

describe('escape.command', () => {
it('should escape meta chars with a caret', () => {
expect(escape.command('foo bar')).toBe('foo^ bar');
expect(escape.command('a&b|c<d>e')).toBe('a^&b^|c^<d^>e');
expect(escape.command('(x)')).toBe('^(x^)');
expect(escape.command('100%')).toBe('100^%');
});

it('should not add surrounding quotes', () => {
expect(escape.command('foo')).toBe('foo');
});
});

describe('escape.argument', () => {
it('should wrap the argument in double quotes and escape meta chars', () => {
expect(escape.argument('foo')).toBe('^"foo^"');
expect(escape.argument('foo bar')).toBe('^"foo^ bar^"');
expect(escape.argument('%PATH%')).toBe('^"^%PATH^%^"');
expect(escape.argument('a&b|c<d>e')).toBe('^"a^&b^|c^<d^>e^"');
expect(escape.argument('(x)')).toBe('^"^(x^)^"');
expect(escape.argument('a,b;c')).toBe('^"a^,b^;c^"');
});

it('should escape a double quote preceded by no backslashes', () => {
expect(escape.argument('a"b')).toBe('^"a\\^"b^"');
});

it('should double backslashes that precede a double quote', () => {
// Two literal backslashes before the quote become four
expect(escape.argument('a\\\\"b')).toBe('^"a\\\\\\\\^"b^"');
});

it('should extend a run of trailing backslashes (current behavior: n become n + 1)', () => {
// Regression coverage for the existing implementation, which was
// deliberately altered in https://github.com/moxystudio/node-cross-spawn/pull/160
const bs = '\\';

expect(escape.argument(`abc${bs.repeat(2)}`)).toBe(`^"abc${bs.repeat(3)}^"`);
expect(escape.argument(`abc${bs}`)).toBe(`^"abc${bs.repeat(2)}^"`);
});

it('should convert non-string input to string', () => {
expect(escape.argument(42)).toBe('^"42^"');
});

it('should double escape meta chars when doubleEscapeMetaChars is set', () => {
expect(escape.argument('a&b', true)).toBe('^^^"a^^^&b^^^"');
});
});
77 changes: 77 additions & 0 deletions lib/util/resolveCommand.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict';

const fs = require('fs');
const os = require('os');
const path = require('path');
const resolveCommand = require('./resolveCommand');

describe('resolveCommand', () => {
let tmpDir;

beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'cross-spawn-test-'));
});

afterEach(() => {
fs.rmSync(tmpDir, { recursive: true, force: true });
});

const makeParsed = (command, options) => ({ command, options: options || {} });

it('should resolve a command on the PATH to an absolute path', () => {
const resolved = resolveCommand(makeParsed('node'));

expect(typeof resolved).toBe('string');
expect(path.isAbsolute(resolved)).toBe(true);
});

it('should return undefined for a command that does not exist', () => {
const resolved = resolveCommand(makeParsed('somecommandthatwillneverexist'));

expect(resolved).toBe(undefined);
});

it('should resolve a command from a custom env PATH', () => {
const binDir = path.join(tmpDir, 'bin');

fs.mkdirSync(binDir);
const exe = path.join(binDir, 'mytool');

fs.writeFileSync(exe, '#!/bin/sh\n', { mode: 0o755 });

const env = { PATH: binDir };
const resolved = resolveCommand(makeParsed('mytool', { env }));

expect(resolved).toBe(exe);
});

it('should switch to a custom cwd while resolving and restore it afterwards', () => {
const cwdBefore = process.cwd();
const customCwd = path.join(tmpDir, 'workdir');

fs.mkdirSync(customCwd);

const resolved = resolveCommand(makeParsed('node', { cwd: customCwd }));

expect(process.cwd()).toBe(cwdBefore);
expect(typeof resolved).toBe('string');
});

it('should resolve relative to the custom cwd', () => {
const customCwd = path.join(tmpDir, 'workdir');

fs.mkdirSync(customCwd);
const binDir = path.join(tmpDir, 'bin');

fs.mkdirSync(binDir);
const exe = path.join(binDir, 'mytool');

fs.writeFileSync(exe, '#!/bin/sh\n', { mode: 0o755 });

const env = { PATH: binDir };
const resolved = resolveCommand(makeParsed('mytool', { env, cwd: customCwd }));

// Resolved absolute path is based on the custom cwd
expect(resolved).toBe(path.resolve(customCwd, exe));
});
});