Repository: mochajs/mocha

Problem statement:
`--inspect` flag doesn't work
When I tried to debug mocha with `--inspect` option, `ReferenceError: describe is not defined` error occurred. 
 It seems it is not run under `mocha` context.

```sh
$ ./node_modules/.bin/mocha --version
6.0.0-1

$ ./node_modules/.bin/mocha --inspect ex.test.js
Debugger listening on ws://127.0.0.1:9229/aca2ad85-76ab-42ac-99bc-6acb84bd1bc2
For help, see: https://nodejs.org/en/docs/inspector
/Users/outsider/temp/mocha-next/6/ex.test.js:1
(function (exports, require, module, __filename, __dirname) { describe('Suite', () => {
                                                              ^

ReferenceError: describe is not defined
    at Object.<anonymous> (/Users/outsider/temp/mocha-next/6/ex.test.js:1:63)
    at Module._compile (internal/modules/cjs/loader.js:689:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:742:12)
    at startup (internal/bootstrap/node.js:283:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:743:3)
```

With `mocha@5.2.0`, it is fine.

```sh
$ ./node_modules/.bin/mocha --version
5.2.0

$ ./node_modules/.bin/mocha --inspect ex.test.js
Debugger listening on ws://127.0.0.1:9229/4b56feea-1711-43bd-82a3-5a587171122f
For help, see: https://nodejs.org/en/docs/inspector


  Suite
    ✓ test


  1 passing (5ms)
```


Hints:
I tried to fix this issue, but I could find a good solution. Any idea? /cc @mochajs/core 

```sh
$ ./bin/mocha --inspect example.js

  mocha:cli exec /Users/outsider/bin/lib/node.js/node-v10.15.0-darwin-x64/bin/node w/ args: [ '--inspect',
  'example.js',
  '/Users/outsider/mocha/bin/_mocha',
  '--timeout',
  '200',
  '--no-config',
  '--no-package',
  '--no-opts',
  '--require',
  'test/setup',
  '--ui',
  'bdd',
  '--global',
  'okGlobalA',
  '--global',
  'okGlobalB',
  '--global',
  'okGlobalC',
  '--global',
  'callback*',
  '--diff',
  '--extension',
  'js',
  '--reporter',
  'spec',
  '--slow',
  '75' ] +2ms
Debugger listening on ws://127.0.0.1:9229/296e0480-0771-4977-b4cd-539b3795ad71
For help, see: https://nodejs.org/en/docs/inspector

ReferenceError: describe is not defined
```

In [here](https://github.com/mochajs/mocha/blob/master/bin/mocha#L24), [yargs-parser](https://github.com/yargs/yargs-parser) parsed `example.js` as `--inspect`'s value. Other [Node flags](https://nodejs.org/api/cli.html#cli_options) are same.

So, below works.

```sh
$ ./bin/mocha --inspect --exit example.js

  mocha:cli exec /Users/outsider/bin/lib/node.js/node-v10.15.0-darwin-x64/bin/node w/ args: [ '--inspect',
  '/Users/outsider/mocha/bin/_mocha',
  'example.js',
  '--timeout',
  '200',
  '--exit',
  '--no-config',
  '--no-package',
  '--no-opts',
  '--require',
  'test/setup',
  '--ui',
  'bdd',
  '--global',
  'okGlobalA',
  '--global',
  'okGlobalB',
  '--global',
  'okGlobalC',
  '--global',
  'callback*',
  '--diff',
  '--extension',
  'js',
  '--reporter',
  'spec',
  '--slow',
  '75' ] +2ms
Debugger listening on ws://127.0.0.1:9229/a2e27603-7321-4eb3-97dd-f0e64f224f2a
For help, see: https://nodejs.org/en/docs/inspector

  1 passing (3ms)
```

And this also works.

```sh
$ ./bin/mocha example.js --inspector

  mocha:cli exec /Users/outsider/bin/lib/node.js/node-v10.15.0-darwin-x64/bin/node w/ args: [ '--inspect',
  '/Users/outsider/mocha/bin/_mocha',
  'example.js',
  '--timeout',
  '200',
  '--no-config',
  '--no-package',
  '--no-opts',
  '--require',
  'test/setup',
  '--ui',
  'bdd',
  '--global',
  'okGlobalA',
  '--global',
  'okGlobalB',
  '--global',
  'okGlobalC',
  '--global',
  'callback*',
  '--diff',
  '--extension',
  'js',
  '--reporter',
  'spec',
  '--slow',
  '75' ] +2ms
Debugger listening on ws://127.0.0.1:9229/a09fc498-5626-4023-94bd-e7eb1c8352e7
For help, see: https://nodejs.org/en/docs/inspector

  1 passing (2ms)
```

However this doesn't work, because [yargs-unparser](https://github.com/yargs/yargs-unparser) unparsed `--inspect: 27.0.0.1:1234` as `--inspect 127.0.0.1:1234` not `--inspect=127.0.0.1:1234` in [here]([](https://github.com/mochajs/mocha/blob/master/bin/mocha#L90)).

```sh
$ ./bin/mocha example.js --inspect=127.0.0.1:1234

  mocha:cli exec /Users/outsider/bin/lib/node.js/node-v10.15.0-darwin-x64/bin/node w/ args: [ '--inspect',
  '127.0.0.1:1234',
  '/Users/outsider/mocha/bin/_mocha',
  'example.js',
  '--timeout',
  '200',
  '--no-config',
  '--no-package',
  '--no-opts',
  '--require',
  'test/setup',
  '--ui',
  'bdd',
  '--global',
  'okGlobalA',
  '--global',
  'okGlobalB',
  '--global',
  'okGlobalC',
  '--global',
  'callback*',
  '--diff',
  '--extension',
  'js',
  '--reporter',
  'spec',
  '--slow',
  '75' ] +2ms
Debugger listening on ws://127.0.0.1:9229/a77b7761-4aad-42fb-ba61-a96ab2c4490b
For help, see: https://nodejs.org/en/docs/inspecto

internal/modules/cjs/loader.js:583
    throw err;
    ^

Error: Cannot find module '/Users/outsider/mocha/127.0.0.1:1234'
```
@outsideris see here https://github.com/mochajs/mocha/blob/0f95a7d73355ce9ab299c9f8dfa983b897ecf2fc/lib/cli/options.js#L290-L294

we may want to do the opposite--take everything from `_` and put it into `--spec`.  (`--spec` is an alias for the default positional arguments; the "files" in other words)

does that help?


Have a look at https://github.com/mochajs/mocha/issues/3683#issuecomment-457184246
Maybe your issue has the same cause as mine.

looks like we may just need to special-case this.
basically that involves doing something like what v5.2.0 does for some node flags that optionally accept an argument.

FWIW, `--inspect 0.0.0.0:12345 foo.spec.js` does *not* work on Mocha v5.2.0 either, and I wouldn't expect it to; the `=` is required.
We could require users to distinguish between mocha flags and node flags. All node flags have to be attached at the end of CL, separated by the end parsing sign "--". 
Mocha would just pass these node flags to Node, without any checks or whatever. Node would do the parsing for us.
Like "npm" running a script: `npm run server -- --port=1337`
@juergba 	That'd be easier to implement, but also it'd break backwards-compat, so we should probably just make it work.
I didn't find a proper approach even if I'm working on it.

Patch:
diff --git a/lib/cli/node-flags.js b/lib/cli/node-flags.js
--- a/lib/cli/node-flags.js
+++ b/lib/cli/node-flags.js
@@ -7,6 +7,7 @@
  */
 
 const nodeFlags = require('node-environment-flags');
+const unparse = require('yargs-unparser');
 
 /**
  * These flags are considered "debug" flags.
@@ -34,6 +35,7 @@ const debugFlags = new Set(['debug', 'debug-brk', 'inspect', 'inspect-brk']);
 exports.isNodeFlag = flag =>
   !/^(?:require|r)$/.test(flag) &&
   (nodeFlags.has(flag) ||
+    debugFlags.has(flag) ||
     /(?:preserve-symlinks(?:-main)?|harmony(?:[_-]|$)|(?:trace[_-].+$)|gc(?:[_-]global)?$|es[_-]staging$|use[_-]strict$|v8[_-](?!options).+?$)/.test(
       flag
     ));
@@ -46,3 +48,22 @@ exports.isNodeFlag = flag =>
  * @private
  */
 exports.impliesNoTimeouts = flag => debugFlags.has(flag);
+
+/**
+ * All non-strictly-boolean arguments to node--those with values--must specify those values using `=`, e.g., `--inspect=0.0.0.0`.
+ * Unparse these arguments using `yargs-unparser` (which would result in `--inspect 0.0.0.0`), then supply `=` where we have values.
+ * There's probably an easier or more robust way to do this; fixes welcome
+ * @param {Object} opts - Arguments object
+ * @returns {string[]} Unparsed arguments using `=` to specify values
+ * @private
+ */
+exports.unparseNodeFlags = opts => {
+  var args = unparse(opts);
+  return args.length
+    ? args
+        .join(' ')
+        .split(/\b/)
+        .map(arg => (arg === ' ' ? '=' : arg))
+        .join('')
+    : [];
+};
diff --git a/lib/cli/options.js b/lib/cli/options.js
--- a/lib/cli/options.js
+++ b/lib/cli/options.js
@@ -18,6 +18,7 @@ const findup = require('findup-sync');
 const {deprecate} = require('../utils');
 const debug = require('debug')('mocha:cli:options');
 const {createMissingArgumentError} = require('../errors');
+const {isNodeFlag} = require('./node-flags');
 
 /**
  * The `yargs-parser` namespace
@@ -75,22 +76,46 @@ const nargOpts = types.array
  * @ignore
  */
 const parse = (args = [], ...configObjects) => {
-  const result = yargsParser.detailed(
-    args,
-    Object.assign(
-      {
-        configuration,
-        configObjects,
-        coerce: coerceOpts,
-        narg: nargOpts,
-        alias: aliases
-      },
-      types
-    )
+  // save node-specific args for special handling.
+  // 1. when these args have a "=" they should be considered to have values
+  // 2. if they don't, they just boolean flags
+  // 3. to avoid explicitly defining the set of them, we tell yargs-parser they
+  //    are ALL boolean flags.
+  // 4. we can then reapply the values after yargs-parser is done.
+  const nodeArgs = (Array.isArray(args) ? args : args.split(' ')).reduce(
+    (acc, arg) => {
+      const pair = arg.split('=');
+      const flag = pair[0].replace(/^--?/, '');
+      if (isNodeFlag(flag)) {
+        return arg.includes('=')
+          ? acc.concat([[flag, pair[1]]])
+          : acc.concat([[flag, true]]);
+      }
+      return acc;
+    },
+    []
   );
+
+  const result = yargsParser.detailed(args, {
+    configuration,
+    configObjects,
+    coerce: coerceOpts,
+    narg: nargOpts,
+    alias: aliases,
+    string: types.string,
+    array: types.array,
+    number: types.number,
+    boolean: types.boolean.concat(nodeArgs.map(pair => pair[0]))
+  });
   if (result.error) {
     throw createMissingArgumentError(result.error.message);
   }
+
+  // reapply "=" arg values from above
+  nodeArgs.forEach(([key, value]) => {
+    result.argv[key] = value;
+  });
+
   return result.argv;
 };
 


Test patch:
diff --git a/test/assertions.js b/test/assertions.js
--- a/test/assertions.js
+++ b/test/assertions.js
@@ -9,7 +9,7 @@ exports.mixinMochaAssertions = function(expect) {
         return (
           Object.prototype.toString.call(v) === '[object Object]' &&
           typeof v.output === 'string' &&
-          typeof v.code === 'number' &&
+          'code' in v && // may be null
           Array.isArray(v.args)
         );
       }
@@ -59,9 +59,9 @@ exports.mixinMochaAssertions = function(expect) {
       }
     )
     .addAssertion(
-      '<RawResult|RawRunResult|JSONRunResult> [not] to have [completed with] [exit] code <number>',
+      '<RawRunResult|JSONRunResult> [not] to have completed with [exit] code <number>',
       function(expect, result, code) {
-        expect(result, '[not] to have property', 'code', code);
+        expect(result.code, '[not] to be', code);
       }
     )
     .addAssertion(
@@ -295,5 +295,11 @@ exports.mixinMochaAssertions = function(expect) {
       function(expect, result, output) {
         expect(result.output, '[not] to satisfy', output);
       }
+    )
+    .addAssertion(
+      '<RawRunResult|JSONRunResult> to have [exit] code <number>',
+      function(expect, result, code) {
+        expect(result.code, 'to be', code);
+      }
     );
 };
diff --git a/test/integration/helpers.js b/test/integration/helpers.js
--- a/test/integration/helpers.js
+++ b/test/integration/helpers.js
@@ -81,10 +81,10 @@ module.exports = {
     var path;
 
     path = resolveFixturePath(fixturePath);
-    args = args || [];
+    args = (args || []).concat('--reporter', 'json', path);
 
     return invokeMocha(
-      args.concat(['--reporter', 'json', path]),
+      args,
       function(err, res) {
         if (err) return fn(err);
 
@@ -95,8 +95,8 @@ module.exports = {
           fn(
             new Error(
               format(
-                'Failed to parse JSON reporter output.\nArgs: %O\nResult:\n\n%O',
-                args,
+                'Failed to parse JSON reporter output. Error:\n%O\nResponse:\n%O',
+                err,
                 res
               )
             )
diff --git a/test/integration/options/debug.spec.js b/test/integration/options/debug.spec.js
--- a/test/integration/options/debug.spec.js
+++ b/test/integration/options/debug.spec.js
@@ -14,19 +14,117 @@ describe('--debug', function() {
 
     it('should invoke --inspect', function(done) {
       invokeMocha(
-        ['--debug', '--file', DEFAULT_FIXTURE],
+        ['--debug', DEFAULT_FIXTURE],
         function(err, res) {
           if (err) {
             return done(err);
           }
-          expect(res, 'to have passed').and(
+          expect(res, 'to contain output', /Debugger listening/i);
+          done();
+        },
+        'pipe'
+      );
+    });
+
+    it('should invoke --inspect-brk', function(done) {
+      var proc = invokeMocha(
+        ['--debug-brk', DEFAULT_FIXTURE],
+        function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          expect(res, 'to contain output', /Debugger listening/i);
+          done();
+        },
+        'pipe'
+      );
+
+      // debugger must be manually killed
+      setTimeout(function() {
+        process.kill(proc.pid, 'SIGINT');
+      }, 2000);
+    });
+
+    it('should respect custom host/port', function(done) {
+      invokeMocha(
+        ['--debug=127.0.0.1:9229', DEFAULT_FIXTURE],
+        function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          expect(
+            res,
+            'to contain output',
+            /Debugger listening on .*127.0.0.1:9229/i
+          );
+          done();
+        },
+        'pipe'
+      );
+    });
+
+    it('should warn about incorrect usage for version', function(done) {
+      invokeMocha(
+        ['--debug=127.0.0.1:9229', DEFAULT_FIXTURE],
+        function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          expect(res, 'to contain output', /"--debug" is not available/i);
+          done();
+        },
+        'pipe'
+      );
+    });
+  });
+
+  describe('Node.js v6', function() {
+    // note that v6.3.0 and newer supports --inspect but still supports --debug.
+    before(function() {
+      if (process.version.substring(0, 2) !== 'v6') {
+        this.skip();
+      }
+    });
+
+    it('should start debugger', function(done) {
+      var proc = invokeMocha(
+        ['--debug', DEFAULT_FIXTURE],
+        function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          expect(res, 'to contain output', /Debugger listening/i);
+          done();
+        },
+        'pipe'
+      );
+
+      // debugger must be manually killed
+      setTimeout(function() {
+        process.kill(proc.pid, 'SIGINT');
+      }, 2000);
+    });
+
+    it('should respect custom host/port', function(done) {
+      var proc = invokeMocha(
+        ['--debug=127.0.0.1:9229', DEFAULT_FIXTURE],
+        function(err, res) {
+          if (err) {
+            return done(err);
+          }
+          expect(
+            res,
             'to contain output',
-            /Debugger listening/i
+            /Debugger listening on .*127.0.0.1:9229/i
           );
           done();
         },
-        {stdio: 'pipe'}
+        'pipe'
       );
+
+      setTimeout(function() {
+        process.kill(proc.pid, 'SIGINT');
+      }, 2000);
     });
   });
 });