Closed rensbaardman closed 2 years ago
I think I found the problem: clearly, console
is not appropriately declared with var
by getImportGlobalsSrc.js
, else modifications by __set__
wouldn't have leaked.
Now, getImportGlobalsSrc.js
tries to find all globals by using for (key in globalObj)
, which only goes over all enumerable properties of globalObj
(e.g. those in Object.keys(globalObj)
). Apparently, console
was an enumerable property of global
in Node 9, but not in Node >= 10:
$ nvm use 9
Now using node v9.11.2 (npm v5.6.0)
$ node
> Object.keys(global)
[ 'console',
'DTRACE_NET_SERVER_CONNECTION',
'DTRACE_NET_STREAM_END',
'DTRACE_HTTP_SERVER_REQUEST',
'DTRACE_HTTP_SERVER_RESPONSE',
'DTRACE_HTTP_CLIENT_REQUEST',
'DTRACE_HTTP_CLIENT_RESPONSE',
'global',
'process',
'Buffer',
'clearImmediate',
'clearInterval',
'clearTimeout',
'setImmediate',
'setInterval',
'setTimeout',
'module',
'require' ]
> .exit
$ nvm use 10
Now using node v10.16.0 (npm v6.9.0)
$ node
> Object.keys(global)
[ 'DTRACE_NET_SERVER_CONNECTION',
'DTRACE_NET_STREAM_END',
'DTRACE_HTTP_SERVER_REQUEST',
'DTRACE_HTTP_SERVER_RESPONSE',
'DTRACE_HTTP_CLIENT_REQUEST',
'DTRACE_HTTP_CLIENT_RESPONSE',
'global',
'process',
'Buffer',
'clearImmediate',
'clearInterval',
'clearTimeout',
'setImmediate',
'setInterval',
'setTimeout' ]
Note that console
is missing in the second case! (along with some other variables)
update: for those interested, console
was made non-enumerable in https://github.com/nodejs/node/pull/17708 because apparantly that's what the standards say it should be
I think however, that simply adding console
to the 'list-of-global-vars' isn't enough: there are also a lot of global variables that aren't enumerable, and which we would miss in that case:
$ node
> console.dir(Object.getOwnPropertyNames(global), {'maxArrayLength': null} )
[ 'Object',
'Function',
'Array',
'Number',
'parseFloat',
'parseInt',
'Infinity',
'NaN',
'undefined',
'Boolean',
'String',
'Symbol',
'Date',
'Promise',
'RegExp',
'Error',
'EvalError',
'RangeError',
'ReferenceError',
'SyntaxError',
'TypeError',
'URIError',
'JSON',
'Math',
'console',
'Intl',
'ArrayBuffer',
'Uint8Array',
'Int8Array',
'Uint16Array',
'Int16Array',
'Uint32Array',
'Int32Array',
'Float32Array',
'Float64Array',
'Uint8ClampedArray',
'BigUint64Array',
'BigInt64Array',
'DataView',
'Map',
'Set',
'WeakMap',
'WeakSet',
'Proxy',
'Reflect',
'decodeURI',
'decodeURIComponent',
'encodeURI',
'encodeURIComponent',
'escape',
'unescape',
'eval',
'isFinite',
'isNaN',
'SharedArrayBuffer',
'Atomics',
'BigInt',
'WebAssembly',
'DTRACE_NET_SERVER_CONNECTION',
'DTRACE_NET_STREAM_END',
'DTRACE_HTTP_SERVER_REQUEST',
'DTRACE_HTTP_SERVER_RESPONSE',
'DTRACE_HTTP_CLIENT_REQUEST',
'DTRACE_HTTP_CLIENT_RESPONSE',
'global',
'process',
'GLOBAL',
'root',
'Buffer',
'clearImmediate',
'clearInterval',
'clearTimeout',
'setImmediate',
'setInterval',
'setTimeout',
'URL',
'URLSearchParams',
'module',
'require',
'assert',
'async_hooks',
'buffer',
'child_process',
'cluster',
'crypto',
'dgram',
'dns',
'domain',
'events',
'fs',
'http',
'http2',
'https',
'inspector',
'net',
'os',
'path',
'perf_hooks',
'punycode',
'querystring',
'readline',
'repl',
'stream',
'string_decoder',
'tls',
'trace_events',
'tty',
'url',
'util',
'v8',
'vm',
'zlib',
'_',
'_error' ]
(that is Node 10 output, Node 9 is similar but missing BigUint64Array
, BigInt64Array
, BigInt
, URL
, URLSearchParams
, and trace_events
.)
Note that this one does include console
.
The fix should be simple: replace for (key in globalObj)
with for (key of Object.getOwnPropertyNames(globalObj))
. But I'm a little hesitant since I don't know if there's anything else to consider when declaring all these globals with var
.
An extra test has to be added too, to cover both enumerable and non-enumerable globals. Care should be taken that the chosen example globals are available in all versions of Node under test, and are always either enumerable or non-enumerable (so console
is not suited).
And – but I've never worked with them, so I am not sure how relevant they are – consider symbols:
$ node
> Object.getOwnPropertySymbols(global)
[ Symbol(Symbol.toStringTag) ]
They are (the only?) global variables that are not included in Object.getOwnPropertyNames(global)
.
Possibly relevant is that in browsers, there are way more global variables
> Object.keys(window)
Array(247) [ "close", "stop", "focus", "blur", "open", "alert", "confirm", "prompt", "print", "postMessage", … ]
> Object.getOwnPropertyNames(window)
Array(826) [ "undefined", "Boolean", "InternalError", "EvalError", "RangeError", "SyntaxError", "TypeError", "URIError", "ArrayBuffer", "Int8Array", … ]
Could declaring them all with var
cause a possible speed/memory problem of any kind?
I think this is relevant to my issue where we are trying to override process.exit
.
It works in Node 10, but stops working in Node 12.
Node 10:
Running node v10.16.0 (npm v6.9.0)
> Object.keys(global)
[ 'global',
'process',
'Buffer',
'clearImmediate',
'clearInterval',
'clearTimeout',
'setImmediate',
'setInterval',
'setTimeout' ]
Node 12:
Welcome to Node.js v12.4.0.
Type ".help" for more information.
> Object.keys(global)
[
'global',
'clearInterval',
'clearTimeout',
'setInterval',
'setTimeout',
'queueMicrotask',
'clearImmediate',
'setImmediate'
]
process
is no longer in global
in Node 12.
And to quote from https://nodejs.org/docs/latest-v12.x/api/process.html#process_process:
The
process
object is aglobal
that provides information about, and control over, the current Node.js process. As a global, it is always available to Node.js applications without usingrequire()
. It can also be explicitly accessed usingrequire()
:const process = require('process');
Does this sound right?
Any other explanations?
Not quite sure how to explain it, I'm not sure on their terminology. When I said "process
is no longer in global
in Node 12", I mean it is no longer on the global
object. It is still "global" in the sense that you don't need to require
it.
Using
__set__({
process : () => {}
});
on NodeJS 12 is broken, however...
__set__("process.exit", () => {});
does work on NodeJS 12 for me.
So, process
is still global, it is only not part of the enumerable keys of global
.
You can check this with
$ nvm use 12
$ node
> Object.getOwnPropertyNames(global).indexOf('process')
which should return an integer (for me, it is 60) indicating it is in that list.
But indeed, the same bug is the cause of your problems. I have a fork that fixes it. I also submitted it as a PR, but it has been a long time since the last PR in this repo was merged.
Note, though, that the workaround to __set__
process.exit
directly is strongly discouraged. Are you sure you are not simply modifying the global process
?
Note, though, that the workaround to set process.exit directly is strongly discouraged. Are you sure you are not simply modifying the global process?
Thanks for the note... I'll forward this to the cordova peeps. We've tried the dot notation workaround and it didn't break existing tests, but would be beneficial to keep an eye on your PR so that we can update asap.
Yes, I can confirm this. This is because they started to make some global objects non-enumerable. I just skimmed https://github.com/jhnns/rewire/pull/168 and it looks like it's a good fix for this.
In case anyone else is having problems with this, in the absence of a fix I've stumbled into a workaround. For each module whose test module is __set__()
ing a global, add something like this at the top:
var {process, console} = global;
and add globals as needed.
One caveat is that this may not work with repackaging tools. For instance, we have a check on if (typeof __webpack_require__ === 'function') {...}
to distinguish between packed and debug code at runtime, and trying the above solution causes problems when running the code packed. The way round that was for the tests to use a real global __webpack_require__
variable, and to have delete global.__webpack_require__;
in an afterEach()
to cleanup.
Published as v6.0.0 🚀
Thank you!!! Version 6.0.0 seems to work for me: there is no longer a leak with global overrides (like Date, which was my case).
When running the test suite with Node 10 or higher, the test
should not influence other modules when injecting mocks
in the shared test cases fails. This happens because__set__
ing a global inmoduleA.js
doesn't set that global only within that module, but globally (so also inmoduleB.js
and in the test suite itself). This should not happen, since it leaks to other tests and can have various unwanted effects. It did not happen with Node 9 (which is why Travis also didn't pick this up - it tests with Node 6, 8 and 9), and I do not know why this happens using higher versions (Node 10, 11 and 12 all have this behaviour, starting from the very first v10.0.0).What makes this case even more mysterious, is that since that test (
should not influence ...
) setsconsole
to a dummy mock{ }
(which should only have effect inmoduleA
, but because of this bug also changes the globalconsole
), all further Mocha output (which usesconsole.log
) is suppressed. It took me a while therefore, to actually notice this test was failing!All other tests seem to pass fine –
it.skip
the failing test, and you see the other tests pass:This is also the easiest way to check that Mocha actually ran the other tests.
How to reproduce
I use
nvm
to switch between node versions. (I left out some of the test results, as marked by...
)Note that the last 20-or-so tests disappeared, and there was no test result '
x passing
'.