CVE-2023-40185: Improper Neutralization of Escape, Meta, or Control Sequences (CWE-150)
value: shescape is simple shell escape library for JavaScript. This may impact users that use Shescape on Windows in a threaded context. The vulnerability can result in Shescape escaping (or quoting) for the wrong shell, thus allowing attackers to bypass protections depending on the combination of expected and used shell. This bug has been patched in version 1.7.4.
CWE description: The product receives input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could be interpreted as escape, meta, or control character sequences when they are sent to a downstream component.
CVSS v3 base score: 8.6
Published: 2023-08-23T21:15Z
Repository: https://github.com/ericcornelissen/shescape
Language: JavaScript
Files changed: CHANGELOG.md, index.js, src/executables.js, src/options.js, src/unix.js, src/win.js, test/_constants.cjs, test/e2e/_.js, test/e2e/_common.js, test/e2e/exec-file.test.js, test/e2e/exec.test.js, test/e2e/fork.test.js, test/e2e/spawn.test.js, test/integration/_generators.js, test/integration/escape-all/bash.test.js, test/integration/escape-all/cmd-exe.test.js, test/integration/escape-all/cmd.test.js, test/integration/escape-all/commonjs.test.js, test/integration/escape-all/csh.test.js, test/integration/escape-all/dash.test.js, test/integration/escape-all/invalid.test.js, test/integration/escape-all/powershell-exe.test.js, test/integration/escape-all/powershell.test.js, test/integration/escape-all/valid.test.js, test/integration/escape-all/zsh.test.js, test/integration/escape/bash.test.js, test/integration/escape/cmd-exe.test.js, test/integration/escape/cmd.test.js, test/integration/escape/commonjs.test.js, test/integration/escape/csh.test.js, test/integration/escape/dash.test.js, test/integration/escape/invalid.test.js, test/integration/escape/powershell-exe.test.js, test/integration/escape/powershell.test.js, test/integration/escape/valid.test.js, test/integration/escape/zsh.test.js, test/integration/quote-all/bash.test.js, test/integration/quote-all/cmd-exe.test.js, test/integration/quote-all/cmd.test.js, test/integration/quote-all/commonjs.test.js, test/integration/quote-all/csh.test.js, test/integration/quote-all/dash.test.js, test/integration/quote-all/invalid.test.js, test/integration/quote-all/powershell-exe.test.js, test/integration/quote-all/powershell.test.js, test/integration/quote-all/valid.test.js, test/integration/quote-all/zsh.test.js, test/integration/quote/bash.test.js, test/integration/quote/cmd-exe.test.js, test/integration/quote/cmd.test.js, test/integration/quote/commonjs.test.js, test/integration/quote/csh.test.js, test/integration/quote/dash.test.js, test/integration/quote/invalid.test.js, test/integration/quote/powershell-exe.test.js, test/integration/quote/powershell.test.js, test/integration/quote/valid.test.js, test/integration/quote/zsh.test.js, test/integration/testing/commonjs.test.js, test/integration/testing/functional.test.js, test/unit/executables/_.js, test/unit/executables/resolve.test.js, test/unit/options/parse-options.test.js, test/unit/unix/index.test.js, test/unit/win/index.test.js
Commit date: 2023-08-21 08:05:50 +0000
Commit message: Provide explicit `$PATH` value to which (#1142)

Update the implementation of `resolveExecutable` to accept the
environment variables so that they can explicitly be provided to
which [1]. All internal code and tests have been updated accordingly,
no external changes.

This is in an effort to fix a problem where environment variables aren't
always passed on correctly to subprocesses. The problem is described in
[2], though if Shescape is used in a forked process on Windows it would
face the same problem. (As a problem found through the test runner, use
of which [1] in tests has been updated similarly.

As a result of this change, the integration and e2e test on Windows are
quite a bit slower (because now they actually look up the executable).
Hence, both of these suites have been updated to use separated test
files in order to better leverage AVA's concurrency - speeding up
individual files and avoiding timeouts as well as running tests quicker
if cores are available.

This change also allowed for including executables without a file
extension in the integration and e2e tests, further improving coverage.
(This replaced the `.EXE` coverage as extensionless executables are
resolved to `.EXE`, thus reducing test count without reducing coverage.)

As a side note, it was funny to see that `getShellName` name unit tests
for both Unix and Windows already generated and provided environment
variables (:

--
1. https://www.npmjs.com/package/which
2. https://github.com/avajs/ava/issues/3014
Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26456999..a3c14905 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,10 +5,11 @@ All notable changes to _Shescape_ will be documented in this file.
 The format is based on [Keep a Changelog], and this project adheres to [Semantic
 Versioning].
 
 ## [Unreleased]
 
+- Fix potential silent executable lookup failure for Windows. ([#1142])
 - Support more valid `shell` values for Windows. ([#1137])
 
 ## [1.7.3] - 2023-08-07
 
 - Change escaping of carriage return characters for CMD and Csh. ([#1094])
@@ -290,8 +291,9 @@ Versioning].
 [#1023]: https://github.com/ericcornelissen/shescape/pull/1023
 [#1082]: https://github.com/ericcornelissen/shescape/pull/1082
 [#1083]: https://github.com/ericcornelissen/shescape/pull/1083
 [#1094]: https://github.com/ericcornelissen/shescape/pull/1094
 [#1137]: https://github.com/ericcornelissen/shescape/pull/1137
+[#1142]: https://github.com/ericcornelissen/shescape/pull/1142
 [552e8ea]: https://github.com/ericcornelissen/shescape/commit/552e8eab56861720b1d4e5474fb65741643358f9
 [keep a changelog]: https://keepachangelog.com/en/1.0.0/
 [semantic versioning]: https://semver.org/spec/v2.0.0.html
diff --git a/index.js b/index.js
index c8360591..9c81e191 100644
--- a/index.js
+++ b/index.js
@@ -51,11 +51,11 @@ function getPlatformHelpers() {
  * @since 0.1.0
  */
 export function escape(arg, options = {}) {
   const helpers = getPlatformHelpers();
   const { flagProtection, interpolation, shellName } = parseOptions(
-    { options, process },
+    { env: process.env, options },
     helpers,
   );
   const argAsString = checkedToString(arg);
   const escape = helpers.getEscapeFunction(shellName, { interpolation });
   const escapedArg = escape(argAsString);
@@ -127,11 +127,11 @@ export function escapeAll(args, options = {}) {
  * @since 0.3.0
  */
 export function quote(arg, options = {}) {
   const helpers = getPlatformHelpers();
   const { flagProtection, shellName } = parseOptions(
-    { options, process },
+    { env: process.env, options },
     helpers,
   );
   const argAsString = checkedToString(arg);
   const [escape, quote] = helpers.getQuoteFunction(shellName);
   const escapedArg = escape(argAsString);
diff --git a/src/executables.js b/src/executables.js
index f9c775d2..3d21aa71 100644
--- a/src/executables.js
+++ b/src/executables.js
@@ -10,20 +10,24 @@
  * To obtain the location of the executable this function (if necessary):
  * - Expands the provided string to an absolute path.
  * - Follows symbolic links.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {string} args.executable A string representation of the executable.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.exists A function to check if a file exists.
  * @param {Function} deps.readlink A function to resolve (sym)links.
  * @param {Function} deps.which A function to perform a `which(1)`-like lookup.
  * @returns {string} The full path to the binary of the executable.
  */
-export function resolveExecutable({ executable }, { exists, readlink, which }) {
+export function resolveExecutable(
+  { env, executable },
+  { exists, readlink, which },
+) {
   try {
-    executable = which(executable);
+    executable = which(executable, { path: env.PATH || env.Path });
   } catch (_) {
     // For backwards compatibility return the executable even if its location
     // cannot be obtained
     return executable;
   }
diff --git a/src/options.js b/src/options.js
index f8dc3e8e..62b9ee22 100644
--- a/src/options.js
+++ b/src/options.js
@@ -8,27 +8,26 @@ import { isString } from "./reflection.js";
 
 /**
  * Parses options provided to shescape.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {object} args.options The options for escaping.
  * @param {boolean} [args.options.flagProtection] Is flag protection enabled.
  * @param {boolean} [args.options.interpolation] Is interpolation enabled.
  * @param {boolean | string} [args.options.shell] The shell to escape for.
- * @param {object} args.process The `process` values.
- * @param {object} args.process.env The environment variables.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.getDefaultShell Function to get the default shell.
  * @param {Function} deps.getShellName Function to get the name of a shell.
  * @returns {object} The parsed arguments.
  */
 export function parseOptions(
-  { options: { flagProtection, interpolation, shell }, process: { env } },
+  { env, options: { flagProtection, interpolation, shell } },
   { getDefaultShell, getShellName },
 ) {
   flagProtection = flagProtection ? true : false;
   interpolation = interpolation ? true : false;
   shell = isString(shell) ? shell : getDefaultShell({ env });
 
-  const shellName = getShellName({ shell }, { resolveExecutable });
+  const shellName = getShellName({ env, shell }, { resolveExecutable });
   return { flagProtection, interpolation, shellName };
 }
diff --git a/src/unix.js b/src/unix.js
index 52c3be8b..d944e11d 100644
--- a/src/unix.js
+++ b/src/unix.js
@@ -119,18 +119,19 @@ export function getFlagProtectionFunction(shellName) {
 
 /**
  * Determines the name of the shell identified by a file path or file name.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {string} args.shell The name or path of the shell.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.resolveExecutable Resolve the path to an executable.
  * @returns {string} The shell name.
  */
-export function getShellName({ shell }, { resolveExecutable }) {
+export function getShellName({ env, shell }, { resolveExecutable }) {
   shell = resolveExecutable(
-    { executable: shell },
+    { env, executable: shell },
     { exists: fs.existsSync, readlink: fs.readlinkSync, which: which.sync },
   );
 
   const shellName = path.basename(shell);
   if (getEscapeFunction(shellName, {}) === undefined) {
diff --git a/src/win.js b/src/win.js
index c9b7ec3d..65b117ec 100644
--- a/src/win.js
+++ b/src/win.js
@@ -96,18 +96,19 @@ export function getFlagProtectionFunction(shellName) {
 
 /**
  * Determines the name of the shell identified by a file path or file name.
  *
  * @param {object} args The arguments for this function.
+ * @param {object} args.env The environment variables.
  * @param {string} args.shell The name or path of the shell.
  * @param {object} deps The dependencies for this function.
  * @param {Function} deps.resolveExecutable Resolve the path to an executable.
  * @returns {string} The shell name.
  */
-export function getShellName({ shell }, { resolveExecutable }) {
+export function getShellName({ env, shell }, { resolveExecutable }) {
   shell = resolveExecutable(
-    { executable: shell },
+    { env, executable: shell },
     { exists: fs.existsSync, readlink: fs.readlinkSync, which: which.sync },
   );
 
   const shellName = path.win32.basename(shell);
   if (getEscapeFunction(shellName, {}) === undefined) {
diff --git a/test/_constants.cjs b/test/_constants.cjs
index 4900def2..6c71c142 100644
--- a/test/_constants.cjs
+++ b/test/_constants.cjs
@@ -58,13 +58,15 @@ module.exports.shellsUnix = [
   module.exports.binZsh,
 ];
 
 /* Windows related constants */
 module.exports.binCmd = "cmd.exe";
+module.exports.binCmdNoExt = "cmd";
 module.exports.binPowerShell = "powershell.exe";
+module.exports.binPowerShellNoExt = "powershell";
 
 module.exports.shellsWindows = [
   module.exports.binCmd,
-  "cmd.EXE",
+  module.exports.binCmdNoExt,
   module.exports.binPowerShell,
-  "powershell.EXE",
+  module.exports.binPowerShellNoExt,
 ];
diff --git a/test/e2e/_.js b/test/e2e/_.js
index 624df0e0..efe6285b 100644
--- a/test/e2e/_.js
+++ b/test/e2e/_.js
@@ -1,10 +1,10 @@
 /**
  * @overview Provides testing utilities.
  * @license MIT
  */
 
-import { injectionStrings } from "../../testing.js";
 import * as constants from "../_constants.cjs";
+import * as common from "./_common.js";
 import * as macros from "./_macros.js";
 
-export { constants, macros, injectionStrings };
+export { common, constants, macros };
diff --git a/test/e2e/_common.js b/test/e2e/_common.js
new file mode 100644
index 00000000..8cdc9d22
--- /dev/null
+++ b/test/e2e/_common.js
@@ -0,0 +1,53 @@
+/**
+ * @overview Provides common utilities for end-to-end tests.
+ * @license MIT
+ */
+
+import process from "node:process";
+
+import test from "ava";
+import isCI from "is-ci";
+import which from "which";
+
+import { injectionStrings } from "../../testing.js";
+import * as constants from "../_constants.cjs";
+
+/**
+ * Get a list of strings to use as arguments in end-to-end tests.
+ *
+ * @returns {string[]} A list of test arguments.
+ */
+export function getTestArgs() {
+  return ["harmless", ...injectionStrings];
+}
+
+/**
+ * Get the AVA test function to use for the given shell.
+ *
+ * @param {string} shell The shell to run a test for.
+ * @returns {Function} An AVA `test` function.
+ */
+export function getTestFn(shell) {
+  try {
+    if (!isCI && typeof shell === "string") {
+      which.sync(shell, { path: process.env.PATH || process.env.Path });
+    }
+
+    return test;
+  } catch (_) {
+    return test.skip;
+  }
+}
+
+/**
+ * Get a list of `shell` option values to use in end-to-end tests.
+ *
+ * @returns {(boolean | string)[]} A list of `shell` option values.
+ */
+export function getTestShells() {
+  const systemShells = constants.isWindows
+    ? constants.shellsWindows
+    : constants.shellsUnix;
+
+  return [false, true, ...systemShells];
+}
diff --git a/test/e2e/child_process.test.js b/test/e2e/child_process.test.js
deleted file mode 100644
index 7c448943..00000000
--- a/test/e2e/child_process.test.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * @overview Contains end-to-end tests of using Shescape with the child_process
- * package.
- * @license MIT
- */
-
-import test from "ava";
-import isCI from "is-ci";
-import which from "which";
-
-import { constants, macros, injectionStrings } from "./_.js";
-
-const systemShells = constants.isWindows
-  ? constants.shellsWindows
-  : constants.shellsUnix;
-
-const testArgs = ["harmless", ...injectionStrings];
-const testShells = [false, true, ...systemShells];
-
-for (const arg of testArgs) {
-  test(macros.fork, { arg });
-
-  for (const shell of testShells) {
-    let runTest = test;
-    try {
-      if (!isCI && typeof shell === "string") {
-        which.sync(shell);
-      }
-    } catch (_) {
-      runTest = test.skip;
-    }
-
-    runTest(macros.exec, { arg, shell });
-    runTest(macros.execSync, { arg, shell });
-    runTest(macros.execFile, { arg, shell });
-    runTest(macros.execFileSync, { arg, shell });
-    runTest(macros.spawn, { arg, shell });
-    runTest(macros.spawnSync, { arg, shell });
-  }
-}
diff --git a/test/e2e/exec-file.test.js b/test/e2e/exec-file.test.js
new file mode 100644
index 00000000..659a04fd
--- /dev/null
+++ b/test/e2e/exec-file.test.js
@@ -0,0 +1,15 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `execFile` (and `execFileSync`) functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const shell of common.getTestShells()) {
+  const test = common.getTestFn(shell);
+  for (const arg of common.getTestArgs()) {
+    test(macros.execFile, { arg, shell });
+    test(macros.execFileSync, { arg, shell });
+  }
+}
diff --git a/test/e2e/exec.test.js b/test/e2e/exec.test.js
new file mode 100644
index 00000000..044db486
--- /dev/null
+++ b/test/e2e/exec.test.js
@@ -0,0 +1,15 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `exec` (and `execSync`) functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const shell of common.getTestShells()) {
+  const test = common.getTestFn(shell);
+  for (const arg of common.getTestArgs()) {
+    test(macros.exec, { arg, shell });
+    test(macros.execSync, { arg, shell });
+  }
+}
diff --git a/test/e2e/fork.test.js b/test/e2e/fork.test.js
new file mode 100644
index 00000000..58c7d282
--- /dev/null
+++ b/test/e2e/fork.test.js
@@ -0,0 +1,12 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `fork` functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const arg of common.getTestArgs()) {
+  const test = common.getTestFn(null);
+  test(macros.fork, { arg });
+}
diff --git a/test/e2e/spawn.test.js b/test/e2e/spawn.test.js
new file mode 100644
index 00000000..b13d4bcc
--- /dev/null
+++ b/test/e2e/spawn.test.js
@@ -0,0 +1,15 @@
+/**
+ * @overview Contains end-to-end tests of using Shescape with the child_process
+ * package's `spawn` (and `spawnSync`) functions.
+ * @license MIT
+ */
+
+import { common, macros } from "./_.js";
+
+for (const shell of common.getTestShells()) {
+  const test = common.getTestFn(shell);
+  for (const arg of common.getTestArgs()) {
+    test(macros.spawn, { arg, shell });
+    test(macros.spawnSync, { arg, shell });
+  }
+}
diff --git a/test/integration/_generators.js b/test/integration/_generators.js
index 2362df64..bac4406d 100644
--- a/test/integration/_generators.js
+++ b/test/integration/_generators.js
@@ -3,32 +3,20 @@
  * @license MIT
  */
 
 import * as fixturesUnix from "../fixtures/unix.js";
 import * as fixturesWindows from "../fixtures/win.js";
-import common from "../_constants.cjs";
 
-/**
- * Returns the shells officially supported by Shescape for the current platform.
- *
- * @yields {string} Supported shells for the current platform.
- */
-export function* platformShells() {
-  if (common.isWindows) {
-    yield* common.shellsWindows;
-  } else {
-    yield* common.shellsUnix;
-  }
-}
+import { constants } from "./_.js";
 
 /**
  * Returns the test fixtures for the current platform.
  *
  * @returns {object} All test fixtures for the current platform.
  */
 function getPlatformFixtures() {
-  if (common.isWindows) {
+  if (constants.isWindows) {
     return fixturesWindows;
   } else {
     return fixturesUnix;
   }
 }
@@ -38,17 +26,20 @@ function getPlatformFixtures() {
  *
  * @param {string} shell The shell to get examples for.
  * @returns {object} All test fixtures for `shell`.
  */
 function getShellFixtures(shell) {
-  shell = shell.toLowerCase();
+  let shellName = shell.toLowerCase();
+  if (constants.isWindows) {
+    shellName = shellName.endsWith(".exe") ? shellName : `${shellName}.exe`;
+  }
 
   const fixtures = getPlatformFixtures();
   return {
-    escape: Object.values(fixtures.escape[shell]).flat(),
-    flag: Object.values(fixtures.flag[shell]).flat(),
-    quote: Object.values(fixtures.quote[shell]).flat(),
+    escape: Object.values(fixtures.escape[shellName]).flat(),
+    flag: Object.values(fixtures.flag[shellName]).flat(),
+    quote: Object.values(fixtures.quote[shellName]).flat(),
   };
 }
 
 /**
  * Generates example fixtures for escaping for the current platform.
diff --git a/test/integration/escape-all/bash.test.js b/test/integration/escape-all/bash.test.js
new file mode 100644
index 00000000..424dbf32
--- /dev/null
+++ b/test/integration/escape-all/bash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all/cmd-exe.test.js b/test/integration/escape-all/cmd-exe.test.js
new file mode 100644
index 00000000..421ca892
--- /dev/null
+++ b/test/integration/escape-all/cmd-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all/cmd.test.js b/test/integration/escape-all/cmd.test.js
new file mode 100644
index 00000000..ad2278d7
--- /dev/null
+++ b/test/integration/escape-all/cmd.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Windows
+ * Command Prompt (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all/commonjs.test.js b/test/integration/escape-all/commonjs.test.js
new file mode 100644
index 00000000..bcced839
--- /dev/null
+++ b/test/integration/escape-all/commonjs.test.js
@@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration test for the CommonJS version of
+ * `shescape.escapeAll`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import * as fc from "fast-check";
+
+import { arbitrary } from "../_.js";
+
+import { escapeAll } from "shescape";
+import { escapeAll as escapeAllCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = escapeAll(args, options);
+    const resultCjs = escapeAllCjs(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);
diff --git a/test/integration/escape-all/csh.test.js b/test/integration/escape-all/csh.test.js
new file mode 100644
index 00000000..b4a611d4
--- /dev/null
+++ b/test/integration/escape-all/csh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all/dash.test.js b/test/integration/escape-all/dash.test.js
new file mode 100644
index 00000000..9c915b9d
--- /dev/null
+++ b/test/integration/escape-all/dash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all/invalid.test.js b/test/integration/escape-all/invalid.test.js
new file mode 100644
index 00000000..74993335
--- /dev/null
+++ b/test/integration/escape-all/invalid.test.js
@@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.escapeAll`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => escapeAll([value], options), { instanceOf: TypeError });
+    t.throws(() => escapeAll(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  escapeAll(["a"], payload);
+});
diff --git a/test/integration/escape-all/powershell-exe.test.js b/test/integration/escape-all/powershell-exe.test.js
new file mode 100644
index 00000000..6ecb607b
--- /dev/null
+++ b/test/integration/escape-all/powershell-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all/powershell.test.js b/test/integration/escape-all/powershell.test.js
new file mode 100644
index 00000000..e6e86225
--- /dev/null
+++ b/test/integration/escape-all/powershell.test.js
@@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for Windows
+ * PowerShell (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(
+    constants.binPowerShellNoExt,
+  )) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape-all.test.js b/test/integration/escape-all/valid.test.js
similarity index 53%
rename from test/integration/escape-all.test.js
rename to test/integration/escape-all/valid.test.js
index 777a7513..fdced9bf 100644
--- a/test/integration/escape-all.test.js
+++ b/test/integration/escape-all/valid.test.js
@@ -1,27 +1,16 @@
 /**
- * @overview Contains integration tests for `shescape.escapeAll`.
+ * @overview Contains integration tests for valid use of `shescape.escapeAll`.
  * @license MIT
  */
 
 import { testProp } from "@fast-check/ava";
-import test from "ava";
 import * as fc from "fast-check";
 
-import { arbitrary, constants, generate, macros } from "./_.js";
+import { arbitrary } from "../_.js";
 
 import { escape, escapeAll } from "shescape";
-import { escapeAll as escapeAllCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`inputs are escaped for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.escapeExamples(shell)) {
-      const result = escapeAll([input], options);
-      t.deepEqual(result, [expected]);
-    }
-  });
-}
 
 testProp(
   "return values",
   [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
   (t, args, options) => {
@@ -69,26 +58,5 @@ testProp(
 
     const entry = result[0];
     t.is(entry, escape(arg, options));
   },
 );
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => escapeAll([value], options), { instanceOf: TypeError });
-    t.throws(() => escapeAll(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  escapeAll(["a"], payload);
-});
-
-testProp(
-  "esm === cjs",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = escapeAll(args, options);
-    const resultCjs = escapeAllCjs(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);
diff --git a/test/integration/escape-all/zsh.test.js b/test/integration/escape-all/zsh.test.js
new file mode 100644
index 00000000..4b5d6732
--- /dev/null
+++ b/test/integration/escape-all/zsh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escapeAll` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escapeAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = escapeAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/escape.test.js b/test/integration/escape.test.js
deleted file mode 100644
index a79b9e54..00000000
--- a/test/integration/escape.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @overview Contains integration tests for `shescape.escape`.
- * @license MIT
- */
-
-import { testProp } from "@fast-check/ava";
-import test from "ava";
-
-import { arbitrary, constants, generate, macros } from "./_.js";
-
-import { escape as escape } from "shescape";
-import { escape as escapeCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`input is escaped for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.escapeExamples(shell)) {
-      const result = escape(input, options);
-      t.is(result, expected);
-    }
-  });
-}
-
-testProp(
-  "return values",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const result = escape(arg, options);
-    t.is(typeof result, "string");
-  },
-);
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => escape(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  escape("a", payload);
-});
-
-testProp(
-  "esm === cjs",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = escape(arg, options);
-    const resultCjs = escapeCjs(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);
diff --git a/test/integration/escape/bash.test.js b/test/integration/escape/bash.test.js
new file mode 100644
index 00000000..d9cbfca6
--- /dev/null
+++ b/test/integration/escape/bash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/cmd-exe.test.js b/test/integration/escape/cmd-exe.test.js
new file mode 100644
index 00000000..83161225
--- /dev/null
+++ b/test/integration/escape/cmd-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/cmd.test.js b/test/integration/escape/cmd.test.js
new file mode 100644
index 00000000..346cc992
--- /dev/null
+++ b/test/integration/escape/cmd.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Windows
+ * Command Prompt (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/commonjs.test.js b/test/integration/escape/commonjs.test.js
new file mode 100644
index 00000000..1ee39631
--- /dev/null
+++ b/test/integration/escape/commonjs.test.js
@@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for the CommonJS version of
+ * `shescape.escape`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { escape } from "shescape";
+import { escape as escapeCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = escape(arg, options);
+    const resultCjs = escapeCjs(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);
diff --git a/test/integration/escape/csh.test.js b/test/integration/escape/csh.test.js
new file mode 100644
index 00000000..88371a8d
--- /dev/null
+++ b/test/integration/escape/csh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/dash.test.js b/test/integration/escape/dash.test.js
new file mode 100644
index 00000000..72c1e534
--- /dev/null
+++ b/test/integration/escape/dash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/invalid.test.js b/test/integration/escape/invalid.test.js
new file mode 100644
index 00000000..5559a323
--- /dev/null
+++ b/test/integration/escape/invalid.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.escape`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { escape } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => escape(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  escape("a", payload);
+});
diff --git a/test/integration/escape/powershell-exe.test.js b/test/integration/escape/powershell-exe.test.js
new file mode 100644
index 00000000..291fe11c
--- /dev/null
+++ b/test/integration/escape/powershell-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/powershell.test.js b/test/integration/escape/powershell.test.js
new file mode 100644
index 00000000..914c01a1
--- /dev/null
+++ b/test/integration/escape/powershell.test.js
@@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for Windows
+ * PowerShell (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.escapeExamples(
+    constants.binPowerShellNoExt,
+  )) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/escape/valid.test.js b/test/integration/escape/valid.test.js
new file mode 100644
index 00000000..e06c7686
--- /dev/null
+++ b/test/integration/escape/valid.test.js
@@ -0,0 +1,19 @@
+/**
+ * @overview Contains integration tests for valid use of `shescape.escape`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { escape } from "shescape";
+
+testProp(
+  "return values",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const result = escape(arg, options);
+    t.is(typeof result, "string");
+  },
+);
diff --git a/test/integration/escape/zsh.test.js b/test/integration/escape/zsh.test.js
new file mode 100644
index 00000000..a5dc7260
--- /dev/null
+++ b/test/integration/escape/zsh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote-all/bash.test.js b/test/integration/quote-all/bash.test.js
new file mode 100644
index 00000000..8d542271
--- /dev/null
+++ b/test/integration/quote-all/bash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all/cmd-exe.test.js b/test/integration/quote-all/cmd-exe.test.js
new file mode 100644
index 00000000..ad453e4b
--- /dev/null
+++ b/test/integration/quote-all/cmd-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all/cmd.test.js b/test/integration/quote-all/cmd.test.js
new file mode 100644
index 00000000..ad17053c
--- /dev/null
+++ b/test/integration/quote-all/cmd.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all/commonjs.test.js b/test/integration/quote-all/commonjs.test.js
new file mode 100644
index 00000000..22360123
--- /dev/null
+++ b/test/integration/quote-all/commonjs.test.js
@@ -0,0 +1,23 @@
+/**
+ * @overview Contains integration test for the CommonJS version of
+ * `shescape.quoteAll`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import * as fc from "fast-check";
+
+import { arbitrary } from "../_.js";
+
+import { quoteAll } from "shescape";
+import { quoteAll as quoteAllCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = quoteAll(args, options);
+    const resultCjs = quoteAllCjs(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);
diff --git a/test/integration/quote-all/csh.test.js b/test/integration/quote-all/csh.test.js
new file mode 100644
index 00000000..f5a7a94a
--- /dev/null
+++ b/test/integration/quote-all/csh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all/dash.test.js b/test/integration/quote-all/dash.test.js
new file mode 100644
index 00000000..6d8df8ab
--- /dev/null
+++ b/test/integration/quote-all/dash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all/invalid.test.js b/test/integration/quote-all/invalid.test.js
new file mode 100644
index 00000000..3551edae
--- /dev/null
+++ b/test/integration/quote-all/invalid.test.js
@@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => quoteAll([value], options), { instanceOf: TypeError });
+    t.throws(() => quoteAll(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  quoteAll(["a"], payload);
+});
diff --git a/test/integration/quote-all/powershell-exe.test.js b/test/integration/quote-all/powershell-exe.test.js
new file mode 100644
index 00000000..eeacf5e7
--- /dev/null
+++ b/test/integration/quote-all/powershell-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all/powershell.test.js b/test/integration/quote-all/powershell.test.js
new file mode 100644
index 00000000..2a9e50cc
--- /dev/null
+++ b/test/integration/quote-all/powershell.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShellNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote-all.test.js b/test/integration/quote-all/valid.test.js
similarity index 54%
rename from test/integration/quote-all.test.js
rename to test/integration/quote-all/valid.test.js
index ac64a82f..2ad79059 100644
--- a/test/integration/quote-all.test.js
+++ b/test/integration/quote-all/valid.test.js
@@ -1,27 +1,16 @@
 /**
- * @overview Contains integration tests for `shescape.quoteAll`.
+ * @overview Contains integration tests for valid use of `shescape.quoteAll`.
  * @license MIT
  */
 
 import { testProp } from "@fast-check/ava";
-import test from "ava";
 import * as fc from "fast-check";
 
-import { arbitrary, constants, generate, macros } from "./_.js";
+import { arbitrary } from "../_.js";
 
 import { quote, quoteAll as quoteAll } from "shescape";
-import { quoteAll as quoteAllCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`inputs are quoted for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.quoteExamples(shell)) {
-      const result = quoteAll([input], options);
-      t.deepEqual(result, [expected]);
-    }
-  });
-}
 
 testProp(
   "return values",
   [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
   (t, args, options) => {
@@ -69,26 +58,5 @@ testProp(
 
     const entry = result[0];
     t.is(entry, quote(arg, options));
   },
 );
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => quoteAll([value], options), { instanceOf: TypeError });
-    t.throws(() => quoteAll(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  quoteAll(["a"], payload);
-});
-
-testProp(
-  "esm === cjs",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = quoteAll(args, options);
-    const resultCjs = quoteAllCjs(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);
diff --git a/test/integration/quote-all/zsh.test.js b/test/integration/quote-all/zsh.test.js
new file mode 100644
index 00000000..6ba7961c
--- /dev/null
+++ b/test/integration/quote-all/zsh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quoteAll` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quoteAll } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = quoteAll([input], options);
+    t.deepEqual(result, [expected]);
+  }
+});
diff --git a/test/integration/quote.test.js b/test/integration/quote.test.js
deleted file mode 100644
index 9f9e022c..00000000
--- a/test/integration/quote.test.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/**
- * @overview Contains integration tests for `shescape.quote`.
- * @license MIT
- */
-
-import { testProp } from "@fast-check/ava";
-import test from "ava";
-
-import { arbitrary, constants, generate, macros } from "./_.js";
-
-import { quote as quote } from "shescape";
-import { quote as quoteCjs } from "../../index.cjs";
-
-for (const shell of generate.platformShells()) {
-  test(`input is quoted for ${shell}`, (t) => {
-    for (const { expected, input, options } of generate.quoteExamples(shell)) {
-      const result = quote(input, options);
-      t.is(result, expected);
-    }
-  });
-}
-
-testProp(
-  "return value",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const result = quote(arg, options);
-    t.is(typeof result, "string");
-  },
-);
-
-testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
-  for (const { value } of constants.illegalArguments) {
-    t.throws(() => quote(value, options), { instanceOf: TypeError });
-  }
-});
-
-test(macros.prototypePollution, (_, payload) => {
-  quote("a", payload);
-});
-
-testProp(
-  "esm === cjs",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = quote(arg, options);
-    const resultCjs = quoteCjs(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);
diff --git a/test/integration/quote/bash.test.js b/test/integration/quote/bash.test.js
new file mode 100644
index 00000000..4f17e5db
--- /dev/null
+++ b/test/integration/quote/bash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.escape` for the
+ * Bourne-again shell (Bash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { escape } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binBash}`, (t) => {
+  for (const scenario of generate.escapeExamples(constants.binBash)) {
+    const { expected, input, options } = scenario;
+    const result = escape(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/cmd-exe.test.js b/test/integration/quote/cmd-exe.test.js
new file mode 100644
index 00000000..11329a9b
--- /dev/null
+++ b/test/integration/quote/cmd-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Windows
+ * Command Prompt (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmd}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmd)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/cmd.test.js b/test/integration/quote/cmd.test.js
new file mode 100644
index 00000000..dbc6fbb7
--- /dev/null
+++ b/test/integration/quote/cmd.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Windows
+ * Command Prompt (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCmdNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/commonjs.test.js b/test/integration/quote/commonjs.test.js
new file mode 100644
index 00000000..ff4d57e6
--- /dev/null
+++ b/test/integration/quote/commonjs.test.js
@@ -0,0 +1,22 @@
+/**
+ * @overview Contains integration tests for the CommonJS version of
+ * `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { quote } from "shescape";
+import { quote as quoteCjs } from "../../../index.cjs";
+
+testProp(
+  "esm === cjs",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = quote(arg, options);
+    const resultCjs = quoteCjs(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);
diff --git a/test/integration/quote/csh.test.js b/test/integration/quote/csh.test.js
new file mode 100644
index 00000000..ccbd96c4
--- /dev/null
+++ b/test/integration/quote/csh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the C shell
+ * (csh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binCsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binCsh)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/dash.test.js b/test/integration/quote/dash.test.js
new file mode 100644
index 00000000..0b338e49
--- /dev/null
+++ b/test/integration/quote/dash.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Debian
+ * Almquist shell (Dash).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binDash}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binDash)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/invalid.test.js b/test/integration/quote/invalid.test.js
new file mode 100644
index 00000000..2a1f4658
--- /dev/null
+++ b/test/integration/quote/invalid.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for invalid use of `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import test from "ava";
+
+import { arbitrary, constants, macros } from "../_.js";
+
+import { quote } from "shescape";
+
+testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
+  for (const { value } of constants.illegalArguments) {
+    t.throws(() => quote(value, options), { instanceOf: TypeError });
+  }
+});
+
+test(macros.prototypePollution, (_, payload) => {
+  quote("a", payload);
+});
diff --git a/test/integration/quote/powershell-exe.test.js b/test/integration/quote/powershell-exe.test.js
new file mode 100644
index 00000000..af2a6415
--- /dev/null
+++ b/test/integration/quote/powershell-exe.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for Windows
+ * PowerShell (with extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShell)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/powershell.test.js b/test/integration/quote/powershell.test.js
new file mode 100644
index 00000000..ffbe173d
--- /dev/null
+++ b/test/integration/quote/powershell.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for Windows
+ * PowerShell (without extension).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test : test.skip;
+
+runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binPowerShellNoExt)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/quote/valid.test.js b/test/integration/quote/valid.test.js
new file mode 100644
index 00000000..3cc9d86d
--- /dev/null
+++ b/test/integration/quote/valid.test.js
@@ -0,0 +1,19 @@
+/**
+ * @overview Contains integration tests for valid use of `shescape.quote`.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+
+import { arbitrary } from "../_.js";
+
+import { quote } from "shescape";
+
+testProp(
+  "return value",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const result = quote(arg, options);
+    t.is(typeof result, "string");
+  },
+);
diff --git a/test/integration/quote/zsh.test.js b/test/integration/quote/zsh.test.js
new file mode 100644
index 00000000..c9ab674e
--- /dev/null
+++ b/test/integration/quote/zsh.test.js
@@ -0,0 +1,21 @@
+/**
+ * @overview Contains integration tests for `shescape.quote` for the Z shell
+ * (Zsh).
+ * @license MIT
+ */
+
+import test from "ava";
+
+import { constants, generate } from "../_.js";
+
+import { quote } from "shescape";
+
+const runTest = constants.isWindows ? test.skip : test;
+
+runTest(`input is escaped for ${constants.binZsh}`, (t) => {
+  for (const scenario of generate.quoteExamples(constants.binZsh)) {
+    const { expected, input, options } = scenario;
+    const result = quote(input, options);
+    t.is(result, expected);
+  }
+});
diff --git a/test/integration/testing/commonjs.test.js b/test/integration/testing/commonjs.test.js
new file mode 100644
index 00000000..d09e055b
--- /dev/null
+++ b/test/integration/testing/commonjs.test.js
@@ -0,0 +1,53 @@
+/**
+ * @overview Contains integration tests for the CommonJS version of the testing
+ * implementation of shescape.
+ * @license MIT
+ */
+
+import { testProp } from "@fast-check/ava";
+import * as fc from "fast-check";
+
+import { arbitrary } from "../_.js";
+
+import { shescape as stubscape } from "shescape/testing";
+import { shescape as stubscapeCjs } from "../../../testing.cjs";
+
+testProp(
+  "escape (esm === cjs)",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = stubscape.escape(arg, options);
+    const resultCjs = stubscapeCjs.escape(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);
+
+testProp(
+  "escapeAll (esm === cjs)",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = stubscape.escapeAll(args, options);
+    const resultCjs = stubscapeCjs.escapeAll(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);
+
+testProp(
+  "quote (esm === cjs)",
+  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
+  (t, arg, options) => {
+    const resultEsm = stubscape.quote(arg, options);
+    const resultCjs = stubscapeCjs.quote(arg, options);
+    t.is(resultEsm, resultCjs);
+  },
+);
+
+testProp(
+  "quoteAll (esm === cjs)",
+  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
+  (t, args, options) => {
+    const resultEsm = stubscape.quoteAll(args, options);
+    const resultCjs = stubscapeCjs.quoteAll(args, options);
+    t.deepEqual(resultEsm, resultCjs);
+  },
+);
diff --git a/test/integration/testing.test.js b/test/integration/testing/functional.test.js
similarity index 63%
rename from test/integration/testing.test.js
rename to test/integration/testing/functional.test.js
index a639dd4d..43ec31df 100644
--- a/test/integration/testing.test.js
+++ b/test/integration/testing/functional.test.js
@@ -5,15 +5,14 @@
  */
 
 import { testProp } from "@fast-check/ava";
 import * as fc from "fast-check";
 
-import { arbitrary } from "./_.js";
+import { arbitrary } from "../_.js";
 
 import * as shescape from "shescape";
 import { shescape as stubscape } from "shescape/testing";
-import { shescape as stubscapeCjs } from "../../testing.cjs";
 
 testProp(
   "escape (stubscape ~ shescape)",
   [fc.anything(), arbitrary.shescapeOptions()],
   (t, arg, options) => {
@@ -102,45 +101,5 @@ testProp(
 
     t.is(errored, stubErrored);
     t.is(typeof result, typeof stubResult);
   },
 );
-
-testProp(
-  "escape (esm === cjs)",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = stubscape.escape(arg, options);
-    const resultCjs = stubscapeCjs.escape(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);
-
-testProp(
-  "escapeAll (esm === cjs)",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = stubscape.escapeAll(args, options);
-    const resultCjs = stubscapeCjs.escapeAll(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);
-
-testProp(
-  "quote (esm === cjs)",
-  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
-  (t, arg, options) => {
-    const resultEsm = stubscape.quote(arg, options);
-    const resultCjs = stubscapeCjs.quote(arg, options);
-    t.is(resultEsm, resultCjs);
-  },
-);
-
-testProp(
-  "quoteAll (esm === cjs)",
-  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
-  (t, args, options) => {
-    const resultEsm = stubscape.quoteAll(args, options);
-    const resultCjs = stubscapeCjs.quoteAll(args, options);
-    t.deepEqual(resultEsm, resultCjs);
-  },
-);
diff --git a/test/unit/executables/_.js b/test/unit/executables/_.js
new file mode 100644
index 00000000..8f57a9db
--- /dev/null
+++ b/test/unit/executables/_.js
@@ -0,0 +1,8 @@
+/**
+ * @overview Provides testing utilities.
+ * @license MIT
+ */
+
+import * as arbitrary from "../../_arbitraries.js";
+
+export { arbitrary };
diff --git a/test/unit/executables/resolve.test.js b/test/unit/executables/resolve.test.js
index a330f982..9ee2f323 100644
--- a/test/unit/executables/resolve.test.js
+++ b/test/unit/executables/resolve.test.js
@@ -1,53 +1,104 @@
 /**
  * @overview Contains unit tests for the functionality to resolve executables.
  * @license MIT
  */
 
+import { testProp } from "@fast-check/ava";
 import test from "ava";
+import * as fc from "fast-check";
 import sinon from "sinon";
 
+import { arbitrary } from "./_.js";
+
 import { resolveExecutable } from "../../../src/executables.js";
 
 test.before((t) => {
   const executable = "/bin/sh";
   const resolvedExecutable = "/path/to/sh";
   const linkedExecutable = "/bin/bash";
   t.not(executable, resolvedExecutable);
   t.not(executable, linkedExecutable);
   t.not(resolvedExecutable, linkedExecutable);
 
-  t.context = { executable, linkedExecutable, resolvedExecutable };
+  const env = {
+    PATH: "/bin:/usr/bin",
+  };
+
+  t.context = { env, executable, linkedExecutable, resolvedExecutable };
 });
 
 test.beforeEach((t) => {
   const exists = sinon.stub();
   const readlink = sinon.stub();
   const which = sinon.stub();
 
   t.context.deps = { exists, readlink, which };
 });
 
+testProp(
+  "env.PATH is defined",
+  [arbitrary.env({ keys: ["PATH", "Path"] }), fc.string({ minLength: 1 })],
+  (t, env, envPath) => {
+    t.context.deps.which.resetHistory();
+
+    env.PATH = envPath;
+
+    const { executable } = t.context;
+    const args = { env, executable };
+
+    resolveExecutable(args, t.context.deps);
+    t.is(t.context.deps.which.callCount, 1);
+    t.true(
+      t.context.deps.which.calledWithExactly(sinon.match.any, {
+        path: env.PATH,
+      }),
+    );
+  },
+);
+
+testProp(
+  "env.PATH is not defined",
+  [arbitrary.env({ keys: ["PATH", "Path"] }), fc.string({ minLength: 1 })],
+  (t, env, envPath) => {
+    t.context.deps.which.resetHistory();
+
+    delete env.PATH;
+    env.Path = envPath;
+
+    const { executable } = t.context;
+    const args = { env, executable };
+
+    resolveExecutable(args, t.context.deps);
+    t.is(t.context.deps.which.callCount, 1);
+    t.true(
+      t.context.deps.which.calledWithExactly(sinon.match.any, {
+        path: env.Path,
+      }),
+    );
+  },
+);
+
 test("the executable cannot be resolved", (t) => {
-  const { executable } = t.context;
-  const args = { executable };
+  const { env, executable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.which.throws();
 
   const result = resolveExecutable(args, t.context.deps);
   t.is(result, executable);
 
   t.is(t.context.deps.which.callCount, 1);
-  t.true(t.context.deps.which.calledWithExactly(executable));
+  t.true(t.context.deps.which.calledWithExactly(executable, sinon.match.any));
 
   t.is(t.context.deps.exists.callCount, 0);
   t.is(t.context.deps.readlink.callCount, 0);
 });
 
 test("the executable doesn't exist", (t) => {
-  const { executable, resolvedExecutable } = t.context;
-  const args = { executable };
+  const { env, executable, resolvedExecutable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.exists.returns(false);
   t.context.deps.which.returns(resolvedExecutable);
 
   const result = resolveExecutable(args, t.context.deps);
@@ -59,12 +110,12 @@ test("the executable doesn't exist", (t) => {
   t.is(t.context.deps.which.callCount, 1);
   t.is(t.context.deps.readlink.callCount, 0);
 });
 
 test("the executable exists and is not a (sym)link", (t) => {
-  const { executable, resolvedExecutable } = t.context;
-  const args = { executable };
+  const { env, executable, resolvedExecutable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.exists.returns(true);
   t.context.deps.readlink.throws();
   t.context.deps.which.returns(resolvedExecutable);
 
@@ -77,12 +128,12 @@ test("the executable exists and is not a (sym)link", (t) => {
   t.is(t.context.deps.which.callCount, 1);
   t.is(t.context.deps.exists.callCount, 1);
 });
 
 test("the executable exists and is a (sym)link", (t) => {
-  const { executable, linkedExecutable, resolvedExecutable } = t.context;
-  const args = { executable };
+  const { env, executable, linkedExecutable, resolvedExecutable } = t.context;
+  const args = { env, executable };
 
   t.context.deps.exists.returns(true);
   t.context.deps.readlink.returns(linkedExecutable);
   t.context.deps.which.returns(resolvedExecutable);
 
diff --git a/test/unit/options/parse-options.test.js b/test/unit/options/parse-options.test.js
index 85668fbd..85a5b320 100644
--- a/test/unit/options/parse-options.test.js
+++ b/test/unit/options/parse-options.test.js
@@ -16,11 +16,11 @@ import { parseOptions } from "../../../src/options.js";
 const arbitraryInput = () =>
   fc
     .tuple(arbitrary.shescapeOptions(), arbitrary.env())
     .map(([options, env]) => {
       options = options || {};
-      return { options, process: { env } };
+      return { env, options };
     });
 
 test.beforeEach((t) => {
   const getDefaultShell = sinon.stub();
   const getShellName = sinon.stub();
@@ -82,20 +82,20 @@ testProp(
     t.context.deps.getDefaultShell.resetHistory();
     t.context.deps.getDefaultShell.returns(defaultShell);
     t.context.deps.getShellName.resetHistory();
     t.context.deps.getShellName.returns(shellName);
 
-    const env = args.process.env;
+    const env = args.env;
     args.options.shell = providedShell;
 
     const result = parseOptions(args, t.context.deps);
     t.is(t.context.deps.getDefaultShell.callCount, 1);
     t.true(t.context.deps.getDefaultShell.calledWithExactly({ env }));
     t.is(t.context.deps.getShellName.callCount, 1);
     t.true(
       t.context.deps.getShellName.calledWithExactly(
-        { shell: defaultShell },
+        { env, shell: defaultShell },
         { resolveExecutable },
       ),
     );
     t.is(result.shellName, shellName);
   },
@@ -106,18 +106,19 @@ testProp(
   [arbitraryInput(), fc.string(), fc.string()],
   (t, args, providedShell, shellName) => {
     t.context.deps.getShellName.resetHistory();
     t.context.deps.getShellName.returns(shellName);
 
+    const env = args.env;
     args.options.shell = providedShell;
 
     const result = parseOptions(args, t.context.deps);
     t.is(t.context.deps.getDefaultShell.callCount, 0);
     t.is(t.context.deps.getShellName.callCount, 1);
     t.true(
       t.context.deps.getShellName.calledWithExactly(
-        { shell: providedShell },
+        { env, shell: providedShell },
         { resolveExecutable },
       ),
     );
     t.is(result.shellName, shellName);
   },
diff --git a/test/unit/unix/index.test.js b/test/unit/unix/index.test.js
index 512b4f0d..af9e88a1 100644
--- a/test/unit/unix/index.test.js
+++ b/test/unit/unix/index.test.js
@@ -104,11 +104,11 @@ testProp(
     resolveExecutable.returns("foobar");
 
     unix.getShellName({ env, shell }, { resolveExecutable });
     t.true(
       resolveExecutable.calledWithExactly(
-        { executable: shell },
+        { env, executable: shell },
         {
           exists: sinon.match.func,
           readlink: sinon.match.func,
           which: sinon.match.func,
         },
diff --git a/test/unit/win/index.test.js b/test/unit/win/index.test.js
index 7fa7cda2..b90d16b2 100644
--- a/test/unit/win/index.test.js
+++ b/test/unit/win/index.test.js
@@ -97,15 +97,19 @@ testProp(
 
 testProp(
   "get shell name for supported shell",
   [arbitrary.env(), arbitrary.windowsPath(), arbitrary.windowsShell()],
   (t, env, basePath, shell) => {
+    const executable = shell.toLowerCase().endsWith(".exe")
+      ? shell
+      : `${shell}.exe`;
+
     const resolveExecutable = sinon.stub();
-    resolveExecutable.returns(path.join(basePath, shell));
+    resolveExecutable.returns(path.join(basePath, executable));
 
     const result = win.getShellName({ env, shell }, { resolveExecutable });
-    t.is(result, shell);
+    t.is(result, executable);
   },
 );
 
 testProp(
   "get shell name for unsupported shell",
@@ -131,11 +135,11 @@ testProp(
     resolveExecutable.returns("foobar");
 
     win.getShellName({ env, shell }, { resolveExecutable });
     t.true(
       resolveExecutable.calledWithExactly(
-        { executable: shell },
+        { env, executable: shell },
         {
           exists: sinon.match.func,
           readlink: sinon.match.func,
           which: sinon.match.func,
         },

Vulnerable code:     { options, process },
    { options, process },
export function resolveExecutable({ executable }, { exists, readlink, which }) {
    executable = which(executable);
 * @param {object} args.process The `process` values.
 * @param {object} args.process.env The environment variables.
  { options: { flagProtection, interpolation, shell }, process: { env } },
  const shellName = getShellName({ shell }, { resolveExecutable });
export function getShellName({ shell }, { resolveExecutable }) {
    { executable: shell },
export function getShellName({ shell }, { resolveExecutable }) {
    { executable: shell },
  "cmd.EXE",
  "powershell.EXE",
import { injectionStrings } from "../../testing.js";
export { constants, macros, injectionStrings };
/**
 * @overview Contains end-to-end tests of using Shescape with the child_process
 * package.
 * @license MIT
 */

import test from "ava";
import isCI from "is-ci";
import which from "which";

import { constants, macros, injectionStrings } from "./_.js";

const systemShells = constants.isWindows
  ? constants.shellsWindows
  : constants.shellsUnix;

const testArgs = ["harmless", ...injectionStrings];
const testShells = [false, true, ...systemShells];

for (const arg of testArgs) {
  test(macros.fork, { arg });

  for (const shell of testShells) {
    let runTest = test;
    try {
      if (!isCI && typeof shell === "string") {
        which.sync(shell);
      }
    } catch (_) {
      runTest = test.skip;
    }

    runTest(macros.exec, { arg, shell });
    runTest(macros.execSync, { arg, shell });
    runTest(macros.execFile, { arg, shell });
    runTest(macros.execFileSync, { arg, shell });
    runTest(macros.spawn, { arg, shell });
    runTest(macros.spawnSync, { arg, shell });
  }
}
import common from "../_constants.cjs";
/**
 * Returns the shells officially supported by Shescape for the current platform.
 *
 * @yields {string} Supported shells for the current platform.
 */
export function* platformShells() {
  if (common.isWindows) {
    yield* common.shellsWindows;
  } else {
    yield* common.shellsUnix;
  }
}
  if (common.isWindows) {
  shell = shell.toLowerCase();
    escape: Object.values(fixtures.escape[shell]).flat(),
    flag: Object.values(fixtures.flag[shell]).flat(),
    quote: Object.values(fixtures.quote[shell]).flat(),
 * @overview Contains integration tests for `shescape.escapeAll`.
import test from "ava";
import { arbitrary, constants, generate, macros } from "./_.js";
import { escapeAll as escapeAllCjs } from "../../index.cjs";

for (const shell of generate.platformShells()) {
  test(`inputs are escaped for ${shell}`, (t) => {
    for (const { expected, input, options } of generate.escapeExamples(shell)) {
      const result = escapeAll([input], options);
      t.deepEqual(result, [expected]);
    }
  });
}

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => escapeAll([value], options), { instanceOf: TypeError });
    t.throws(() => escapeAll(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  escapeAll(["a"], payload);
});

testProp(
  "esm === cjs",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = escapeAll(args, options);
    const resultCjs = escapeAllCjs(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);
/**
 * @overview Contains integration tests for `shescape.escape`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import test from "ava";

import { arbitrary, constants, generate, macros } from "./_.js";

import { escape as escape } from "shescape";
import { escape as escapeCjs } from "../../index.cjs";

for (const shell of generate.platformShells()) {
  test(`input is escaped for ${shell}`, (t) => {
    for (const { expected, input, options } of generate.escapeExamples(shell)) {
      const result = escape(input, options);
      t.is(result, expected);
    }
  });
}

testProp(
  "return values",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const result = escape(arg, options);
    t.is(typeof result, "string");
  },
);

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => escape(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  escape("a", payload);
});

testProp(
  "esm === cjs",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = escape(arg, options);
    const resultCjs = escapeCjs(arg, options);
    t.is(resultEsm, resultCjs);
  },
);
 * @overview Contains integration tests for `shescape.quoteAll`.
import test from "ava";
import { arbitrary, constants, generate, macros } from "./_.js";
import { quoteAll as quoteAllCjs } from "../../index.cjs";

for (const shell of generate.platformShells()) {
  test(`inputs are quoted for ${shell}`, (t) => {
    for (const { expected, input, options } of generate.quoteExamples(shell)) {
      const result = quoteAll([input], options);
      t.deepEqual(result, [expected]);
    }
  });
}

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => quoteAll([value], options), { instanceOf: TypeError });
    t.throws(() => quoteAll(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  quoteAll(["a"], payload);
});

testProp(
  "esm === cjs",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = quoteAll(args, options);
    const resultCjs = quoteAllCjs(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);
/**
 * @overview Contains integration tests for `shescape.quote`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import test from "ava";

import { arbitrary, constants, generate, macros } from "./_.js";

import { quote as quote } from "shescape";
import { quote as quoteCjs } from "../../index.cjs";

for (const shell of generate.platformShells()) {
  test(`input is quoted for ${shell}`, (t) => {
    for (const { expected, input, options } of generate.quoteExamples(shell)) {
      const result = quote(input, options);
      t.is(result, expected);
    }
  });
}

testProp(
  "return value",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const result = quote(arg, options);
    t.is(typeof result, "string");
  },
);

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => quote(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  quote("a", payload);
});

testProp(
  "esm === cjs",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = quote(arg, options);
    const resultCjs = quoteCjs(arg, options);
    t.is(resultEsm, resultCjs);
  },
);
import { arbitrary } from "./_.js";
import { shescape as stubscapeCjs } from "../../testing.cjs";

testProp(
  "escape (esm === cjs)",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = stubscape.escape(arg, options);
    const resultCjs = stubscapeCjs.escape(arg, options);
    t.is(resultEsm, resultCjs);
  },
);

testProp(
  "escapeAll (esm === cjs)",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = stubscape.escapeAll(args, options);
    const resultCjs = stubscapeCjs.escapeAll(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);

testProp(
  "quote (esm === cjs)",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = stubscape.quote(arg, options);
    const resultCjs = stubscapeCjs.quote(arg, options);
    t.is(resultEsm, resultCjs);
  },
);

testProp(
  "quoteAll (esm === cjs)",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = stubscape.quoteAll(args, options);
    const resultCjs = stubscapeCjs.quoteAll(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);
  t.context = { executable, linkedExecutable, resolvedExecutable };
  const { executable } = t.context;
  const args = { executable };
  t.true(t.context.deps.which.calledWithExactly(executable));
  const { executable, resolvedExecutable } = t.context;
  const args = { executable };
  const { executable, resolvedExecutable } = t.context;
  const args = { executable };
  const { executable, linkedExecutable, resolvedExecutable } = t.context;
  const args = { executable };
      return { options, process: { env } };
    const env = args.process.env;
        { shell: defaultShell },
        { shell: providedShell },
        { executable: shell },
    resolveExecutable.returns(path.join(basePath, shell));
    t.is(result, shell);
        { executable: shell },
Fixed code: - Fix potential silent executable lookup failure for Windows. ([#1142])
[#1142]: https://github.com/ericcornelissen/shescape/pull/1142
    { env: process.env, options },
    { env: process.env, options },
 * @param {object} args.env The environment variables.
export function resolveExecutable(
  { env, executable },
  { exists, readlink, which },
) {
    executable = which(executable, { path: env.PATH || env.Path });
 * @param {object} args.env The environment variables.
  { env, options: { flagProtection, interpolation, shell } },
  const shellName = getShellName({ env, shell }, { resolveExecutable });
 * @param {object} args.env The environment variables.
export function getShellName({ env, shell }, { resolveExecutable }) {
    { env, executable: shell },
 * @param {object} args.env The environment variables.
export function getShellName({ env, shell }, { resolveExecutable }) {
    { env, executable: shell },
module.exports.binCmdNoExt = "cmd";
module.exports.binPowerShellNoExt = "powershell";
  module.exports.binCmdNoExt,
  module.exports.binPowerShellNoExt,
import * as common from "./_common.js";
export { common, constants, macros };
/**
 * @overview Provides common utilities for end-to-end tests.
 * @license MIT
 */

import process from "node:process";

import test from "ava";
import isCI from "is-ci";
import which from "which";

import { injectionStrings } from "../../testing.js";
import * as constants from "../_constants.cjs";

/**
 * Get a list of strings to use as arguments in end-to-end tests.
 *
 * @returns {string[]} A list of test arguments.
 */
export function getTestArgs() {
  return ["harmless", ...injectionStrings];
}

/**
 * Get the AVA test function to use for the given shell.
 *
 * @param {string} shell The shell to run a test for.
 * @returns {Function} An AVA `test` function.
 */
export function getTestFn(shell) {
  try {
    if (!isCI && typeof shell === "string") {
      which.sync(shell, { path: process.env.PATH || process.env.Path });
    }

    return test;
  } catch (_) {
    return test.skip;
  }
}

/**
 * Get a list of `shell` option values to use in end-to-end tests.
 *
 * @returns {(boolean | string)[]} A list of `shell` option values.
 */
export function getTestShells() {
  const systemShells = constants.isWindows
    ? constants.shellsWindows
    : constants.shellsUnix;

  return [false, true, ...systemShells];
}
/**
 * @overview Contains end-to-end tests of using Shescape with the child_process
 * package's `execFile` (and `execFileSync`) functions.
 * @license MIT
 */

import { common, macros } from "./_.js";

for (const shell of common.getTestShells()) {
  const test = common.getTestFn(shell);
  for (const arg of common.getTestArgs()) {
    test(macros.execFile, { arg, shell });
    test(macros.execFileSync, { arg, shell });
  }
}
/**
 * @overview Contains end-to-end tests of using Shescape with the child_process
 * package's `exec` (and `execSync`) functions.
 * @license MIT
 */

import { common, macros } from "./_.js";

for (const shell of common.getTestShells()) {
  const test = common.getTestFn(shell);
  for (const arg of common.getTestArgs()) {
    test(macros.exec, { arg, shell });
    test(macros.execSync, { arg, shell });
  }
}
/**
 * @overview Contains end-to-end tests of using Shescape with the child_process
 * package's `fork` functions.
 * @license MIT
 */

import { common, macros } from "./_.js";

for (const arg of common.getTestArgs()) {
  const test = common.getTestFn(null);
  test(macros.fork, { arg });
}
/**
 * @overview Contains end-to-end tests of using Shescape with the child_process
 * package's `spawn` (and `spawnSync`) functions.
 * @license MIT
 */

import { common, macros } from "./_.js";

for (const shell of common.getTestShells()) {
  const test = common.getTestFn(shell);
  for (const arg of common.getTestArgs()) {
    test(macros.spawn, { arg, shell });
    test(macros.spawnSync, { arg, shell });
  }
}
import { constants } from "./_.js";
  if (constants.isWindows) {
  let shellName = shell.toLowerCase();
  if (constants.isWindows) {
    shellName = shellName.endsWith(".exe") ? shellName : `${shellName}.exe`;
  }
    escape: Object.values(fixtures.escape[shellName]).flat(),
    flag: Object.values(fixtures.flag[shellName]).flat(),
    quote: Object.values(fixtures.quote[shellName]).flat(),
/**
 * @overview Contains integration tests for `shescape.escapeAll` for the
 * Bourne-again shell (Bash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binBash}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binBash)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.escapeAll` for the Windows
 * Command Prompt (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmd}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binCmd)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.escapeAll` for the Windows
 * Command Prompt (without extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binCmdNoExt)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration test for the CommonJS version of
 * `shescape.escapeAll`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import * as fc from "fast-check";

import { arbitrary } from "../_.js";

import { escapeAll } from "shescape";
import { escapeAll as escapeAllCjs } from "../../../index.cjs";

testProp(
  "esm === cjs",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = escapeAll(args, options);
    const resultCjs = escapeAllCjs(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);
/**
 * @overview Contains integration tests for `shescape.escapeAll` for the C shell
 * (csh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binCsh}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binCsh)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.escapeAll` for the Debian
 * Almquist shell (Dash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binDash}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binDash)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for invalid use of `shescape.escapeAll`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import test from "ava";

import { arbitrary, constants, macros } from "../_.js";

import { escapeAll } from "shescape";

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => escapeAll([value], options), { instanceOf: TypeError });
    t.throws(() => escapeAll(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  escapeAll(["a"], payload);
});
/**
 * @overview Contains integration tests for `shescape.escapeAll` for Windows
 * PowerShell (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binPowerShell)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.escapeAll` for Windows
 * PowerShell (without extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
  for (const scenario of generate.escapeExamples(
    constants.binPowerShellNoExt,
  )) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
 * @overview Contains integration tests for valid use of `shescape.escapeAll`.
import { arbitrary } from "../_.js";
/**
 * @overview Contains integration tests for `shescape.escapeAll` for the Z shell
 * (Zsh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escapeAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binZsh}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binZsh)) {
    const { expected, input, options } = scenario;
    const result = escapeAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.quote` for the
 * Bourne-again shell (Bash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binBash}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binBash)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.escape` for the Windows
 * Command Prompt (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmd}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binCmd)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.escape` for the Windows
 * Command Prompt (without extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binCmdNoExt)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for the CommonJS version of
 * `shescape.escape`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";

import { arbitrary } from "../_.js";

import { escape } from "shescape";
import { escape as escapeCjs } from "../../../index.cjs";

testProp(
  "esm === cjs",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = escape(arg, options);
    const resultCjs = escapeCjs(arg, options);
    t.is(resultEsm, resultCjs);
  },
);
/**
 * @overview Contains integration tests for `shescape.escape` for the C shell
 * (csh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binCsh}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binCsh)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.escape` for the Debian
 * Almquist shell (Dash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binDash}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binDash)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for invalid use of `shescape.escape`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import test from "ava";

import { arbitrary, constants, macros } from "../_.js";

import { escape } from "shescape";

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => escape(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  escape("a", payload);
});
/**
 * @overview Contains integration tests for `shescape.escape` for Windows
 * PowerShell (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binPowerShell)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.escape` for Windows
 * PowerShell (without extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
  for (const scenario of generate.escapeExamples(
    constants.binPowerShellNoExt,
  )) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for valid use of `shescape.escape`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";

import { arbitrary } from "../_.js";

import { escape } from "shescape";

testProp(
  "return values",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const result = escape(arg, options);
    t.is(typeof result, "string");
  },
);
/**
 * @overview Contains integration tests for `shescape.escape` for the Z shell
 * (Zsh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binZsh}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binZsh)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.quoteAll` for the
 * Bourne-again shell (Bash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binBash}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binBash)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.quoteAll` for the Windows
 * Command Prompt (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmd}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binCmd)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.quoteAll` for the Windows
 * Command Prompt (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binCmdNoExt)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration test for the CommonJS version of
 * `shescape.quoteAll`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import * as fc from "fast-check";

import { arbitrary } from "../_.js";

import { quoteAll } from "shescape";
import { quoteAll as quoteAllCjs } from "../../../index.cjs";

testProp(
  "esm === cjs",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = quoteAll(args, options);
    const resultCjs = quoteAllCjs(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);
/**
 * @overview Contains integration tests for `shescape.quoteAll` for the C shell
 * (csh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binCsh}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binCsh)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.quoteAll` for the Debian
 * Almquist shell (Dash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binDash}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binDash)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for invalid use of `shescape.quote`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import test from "ava";

import { arbitrary, constants, macros } from "../_.js";

import { quoteAll } from "shescape";

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => quoteAll([value], options), { instanceOf: TypeError });
    t.throws(() => quoteAll(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  quoteAll(["a"], payload);
});
/**
 * @overview Contains integration tests for `shescape.quoteAll` for Windows
 * PowerShell (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binPowerShell)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.quoteAll` for Windows
 * PowerShell (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binPowerShellNoExt)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
 * @overview Contains integration tests for valid use of `shescape.quoteAll`.
import { arbitrary } from "../_.js";
/**
 * @overview Contains integration tests for `shescape.quoteAll` for the Z shell
 * (Zsh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quoteAll } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binZsh}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binZsh)) {
    const { expected, input, options } = scenario;
    const result = quoteAll([input], options);
    t.deepEqual(result, [expected]);
  }
});
/**
 * @overview Contains integration tests for `shescape.escape` for the
 * Bourne-again shell (Bash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { escape } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binBash}`, (t) => {
  for (const scenario of generate.escapeExamples(constants.binBash)) {
    const { expected, input, options } = scenario;
    const result = escape(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.quote` for the Windows
 * Command Prompt (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmd}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binCmd)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.quote` for the Windows
 * Command Prompt (without extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binCmdNoExt}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binCmdNoExt)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for the CommonJS version of
 * `shescape.quote`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";

import { arbitrary } from "../_.js";

import { quote } from "shescape";
import { quote as quoteCjs } from "../../../index.cjs";

testProp(
  "esm === cjs",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = quote(arg, options);
    const resultCjs = quoteCjs(arg, options);
    t.is(resultEsm, resultCjs);
  },
);
/**
 * @overview Contains integration tests for `shescape.quote` for the C shell
 * (csh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binCsh}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binCsh)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.quote` for the Debian
 * Almquist shell (Dash).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binDash}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binDash)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for invalid use of `shescape.quote`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import test from "ava";

import { arbitrary, constants, macros } from "../_.js";

import { quote } from "shescape";

testProp("invalid arguments", [arbitrary.shescapeOptions()], (t, options) => {
  for (const { value } of constants.illegalArguments) {
    t.throws(() => quote(value, options), { instanceOf: TypeError });
  }
});

test(macros.prototypePollution, (_, payload) => {
  quote("a", payload);
});
/**
 * @overview Contains integration tests for `shescape.quote` for Windows
 * PowerShell (with extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShell}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binPowerShell)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for `shescape.quote` for Windows
 * PowerShell (without extension).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test : test.skip;

runTest(`input is escaped for ${constants.binPowerShellNoExt}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binPowerShellNoExt)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for valid use of `shescape.quote`.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";

import { arbitrary } from "../_.js";

import { quote } from "shescape";

testProp(
  "return value",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const result = quote(arg, options);
    t.is(typeof result, "string");
  },
);
/**
 * @overview Contains integration tests for `shescape.quote` for the Z shell
 * (Zsh).
 * @license MIT
 */

import test from "ava";

import { constants, generate } from "../_.js";

import { quote } from "shescape";

const runTest = constants.isWindows ? test.skip : test;

runTest(`input is escaped for ${constants.binZsh}`, (t) => {
  for (const scenario of generate.quoteExamples(constants.binZsh)) {
    const { expected, input, options } = scenario;
    const result = quote(input, options);
    t.is(result, expected);
  }
});
/**
 * @overview Contains integration tests for the CommonJS version of the testing
 * implementation of shescape.
 * @license MIT
 */

import { testProp } from "@fast-check/ava";
import * as fc from "fast-check";

import { arbitrary } from "../_.js";

import { shescape as stubscape } from "shescape/testing";
import { shescape as stubscapeCjs } from "../../../testing.cjs";

testProp(
  "escape (esm === cjs)",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = stubscape.escape(arg, options);
    const resultCjs = stubscapeCjs.escape(arg, options);
    t.is(resultEsm, resultCjs);
  },
);

testProp(
  "escapeAll (esm === cjs)",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = stubscape.escapeAll(args, options);
    const resultCjs = stubscapeCjs.escapeAll(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);

testProp(
  "quote (esm === cjs)",
  [arbitrary.shescapeArg(), arbitrary.shescapeOptions()],
  (t, arg, options) => {
    const resultEsm = stubscape.quote(arg, options);
    const resultCjs = stubscapeCjs.quote(arg, options);
    t.is(resultEsm, resultCjs);
  },
);

testProp(
  "quoteAll (esm === cjs)",
  [fc.array(arbitrary.shescapeArg()), arbitrary.shescapeOptions()],
  (t, args, options) => {
    const resultEsm = stubscape.quoteAll(args, options);
    const resultCjs = stubscapeCjs.quoteAll(args, options);
    t.deepEqual(resultEsm, resultCjs);
  },
);
import { arbitrary } from "../_.js";
/**
 * @overview Provides testing utilities.
 * @license MIT
 */

import * as arbitrary from "../../_arbitraries.js";

export { arbitrary };
import { testProp } from "@fast-check/ava";
import * as fc from "fast-check";
import { arbitrary } from "./_.js";

  const env = {
    PATH: "/bin:/usr/bin",
  };

  t.context = { env, executable, linkedExecutable, resolvedExecutable };
testProp(
  "env.PATH is defined",
  [arbitrary.env({ keys: ["PATH", "Path"] }), fc.string({ minLength: 1 })],
  (t, env, envPath) => {
    t.context.deps.which.resetHistory();

    env.PATH = envPath;

    const { executable } = t.context;
    const args = { env, executable };

    resolveExecutable(args, t.context.deps);
    t.is(t.context.deps.which.callCount, 1);
    t.true(
      t.context.deps.which.calledWithExactly(sinon.match.any, {
        path: env.PATH,
      }),
    );
  },
);

testProp(
  "env.PATH is not defined",
  [arbitrary.env({ keys: ["PATH", "Path"] }), fc.string({ minLength: 1 })],
  (t, env, envPath) => {
    t.context.deps.which.resetHistory();

    delete env.PATH;
    env.Path = envPath;

    const { executable } = t.context;
    const args = { env, executable };

    resolveExecutable(args, t.context.deps);
    t.is(t.context.deps.which.callCount, 1);
    t.true(
      t.context.deps.which.calledWithExactly(sinon.match.any, {
        path: env.Path,
      }),
    );
  },
);

  const { env, executable } = t.context;
  const args = { env, executable };
  t.true(t.context.deps.which.calledWithExactly(executable, sinon.match.any));
  const { env, executable, resolvedExecutable } = t.context;
  const args = { env, executable };
  const { env, executable, resolvedExecutable } = t.context;
  const args = { env, executable };
  const { env, executable, linkedExecutable, resolvedExecutable } = t.context;
  const args = { env, executable };
      return { env, options };
    const env = args.env;
        { env, shell: defaultShell },
    const env = args.env;
        { env, shell: providedShell },
        { env, executable: shell },
    const executable = shell.toLowerCase().endsWith(".exe")
      ? shell
      : `${shell}.exe`;

    resolveExecutable.returns(path.join(basePath, executable));
    t.is(result, executable);
        { env, executable: shell },