Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite how common globals are installed in "global" #4904

Merged
merged 3 commits into from
Nov 17, 2017
Merged

Rewrite how common globals are installed in "global" #4904

merged 3 commits into from
Nov 17, 2017

Conversation

mjesun
Copy link
Contributor

@mjesun mjesun commented Nov 16, 2017

This diff rewrites how the fake process variable is created and injected; in order to better mimic the way it looks and improve sandboxing. Now, process is built from Process.prototype, then all the constructors on the prototype chain are executed (i.e. Process, EventEmitter, and Object); and finally the remaining properties are copied as before.

This gives as a result an object that can independently handle events added via process.on, but also passes validations like Object.getPrototypeOf(process) !== Object.prototype.

The risk is that someone is already using process.on to try attaching to the real process; and this will make the test break. But I think it's fine.

value = obj[key];
if (typeof value === 'object' && value !== null) {
value = deepCopy(value);
if (obj.hasOwnProperty(key)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use Object.keys instead?

[Symbol.toStringTag]: 'process',
};
export default (globalObject: Global, globals: ConfigGlobals) => {
let prototype = Object.getPrototypeOf(process);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on https://github.com/nodejs/node/blob/master/lib/process.js I'm assuming there is no other way to get the prototype.

globalObject.Buffer = global.Buffer;

// Make a copy of "process" to avoid memory leaks.
if (typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove the if statement. We now only support versions of node that have symbol and we can assume Symbol.toStringTag is available.

}
}

globalObject.process = processObject;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's factor all the process code out into a separate function and do globalObject.process = createProcessObject(process)?


// Copy all remaining properties that are not already in the object.
for (const key in process) {
if (process.hasOwnProperty(key) && !processObject.hasOwnProperty(key)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Object.keys?

Copy link
Member

@cpojer cpojer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good direction. I think you'll also have to patch jest-runtime to make it so that require('process') will return this process object rather than giving you access to the real process.

People might have code that does process === require('process') and this diff would break it. Please also add an integration test that ensures these two are the same.


import deepCyclicCopy from '../deep_cyclic_copy';

it('returns the same thing for primitive or function values', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"same value"?

import deepCyclicCopy from './deep_cyclic_copy';

export default function() {
const newProcess = deepCyclicCopy(process);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about in this file we do const process = require('process')? Seems nicer.

newProcess[Symbol.toStringTag] = 'process';

// Sequentially execute all constructors over the object.
for (let chain = process; chain; chain = Object.getPrototypeOf(chain)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah what the hell is this for loop. What about do-while?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to change it, it's just very confusing to see "chain;" in there, took me a second :D

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me change it; I don't want confusing code and especially in that file :)

* @flow
*/

export default function deepCyclicCopy(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great function but oh man, it's so hard to clone stuff + it's slow in JS :(

Copy link
Member

@cpojer cpojer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Some small inline comments but feel free to merge. Thanks for making a test and for fixing the bug I created 1.5 years back ;)

@mjesun
Copy link
Contributor Author

mjesun commented Nov 17, 2017

@cpojer I had to add a small hack to make Jasmine work asynchronously.

Jasmine attaches itself to uncaughtException and unhandledRejection. Before sandboxing process, this worked because process inside the context had the same listeners as outside. Now that process is a full copy, this is not a thing anymore, thus the failure.

The proposed solution should not create a leak. The leak with process was created before by tests doing process.on('exit', () => {...}). Now this will happen over the copied process, allowing it to be thrown away; while Jasmine will have access to the real process object.

A proper fix would be to execute all JS outside the context; and just expose describe, it, expect within it. But that requires a quite big refactor, so I'm gonna stick to that fix for now. 😞

@codecov-io
Copy link

codecov-io commented Nov 17, 2017

Codecov Report

Merging #4904 into master will increase coverage by 0.11%.
The diff coverage is 83.33%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4904      +/-   ##
==========================================
+ Coverage   59.77%   59.89%   +0.11%     
==========================================
  Files         195      197       +2     
  Lines        6535     6547      +12     
  Branches        3        3              
==========================================
+ Hits         3906     3921      +15     
+ Misses       2629     2626       -3
Impacted Files Coverage Δ
packages/jest-jasmine2/src/index.js 6.15% <ø> (ø) ⬆️
packages/jest-jasmine2/src/jasmine/Env.js 0% <0%> (ø) ⬆️
packages/jest-util/src/create_process_object.js 100% <100%> (ø)
packages/jest-runtime/src/index.js 73.62% <100%> (-0.08%) ⬇️
packages/jest-util/src/install_common_globals.js 100% <100%> (+15.38%) ⬆️
packages/jest-util/src/deep_cyclic_copy.js 100% <100%> (ø)
packages/jest-resolve/src/index.js 97.34% <0%> (-0.05%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 706b1c9...1e81a05. Read the comment docs.

@cpojer
Copy link
Member

cpojer commented Nov 17, 2017

Instead of introducing another global, let's pass "process" from the parent context into the Jasmine factory here: https://github.com/facebook/jest/blob/master/packages/jest-jasmine2/src/index.js#L39 and thread it through to the place that needs the real process. I'm sorry if this requires a bit more of a refactor but adding a global variable just for this isn't the right way to go about it (I appreciate the hack to unblock though).

@mjesun
Copy link
Contributor Author

mjesun commented Nov 17, 2017

@cpojer commit amended, thanks a lot for the suggestion! Now it looks way better 🙂

@cpojer
Copy link
Member

cpojer commented Nov 17, 2017

This is two million times better.

@cpojer cpojer merged commit 14957ab into jestjs:master Nov 17, 2017
@mjesun mjesun deleted the write-globals branch November 17, 2017 21:37
@mjesun mjesun mentioned this pull request Nov 17, 2017
@github-actions
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Please note this issue tracker is not a help forum. We recommend using StackOverflow or our discord channel for questions.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 13, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants