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

Fix isolateModules not working correctly when module has already been imported #8634

Closed
wants to merge 6 commits into from

Conversation

willym1
Copy link

@willym1 willym1 commented Jul 3, 2019

Summary

This addresses #7863 where any requires inside isolateModules callback were retrieving modules from the regular module registry if the module was previously required in a non-isolated scope.

It didn't work previously because the logic checked for this._moduleRegistry.get(modulePath) FIRST to assign the variable as a regular module, which would always evaluate to true if the module had already been loaded.

The fix was to update the associated if block to check that this._internalModuleRegistry exists FIRST to assign the variable as an isolated module.

Test plan

  • Additional unit tests in runtime_require_module_or_mock.test.js
  • Linked jest-cli to my local sandbox project and ran additional tests (see below)
import incrementor from './incrementor';

describe('incrementor', () => {
  it('gets incrementor from the registry', () => {
    incrementor.increment()

    const cachedIncrementor = require('./incrementor').default

    expect(cachedIncrementor.getState()).toBe(2)
  })

  it('gets isolated incrementor', () => {
    let isolatedIncrementor

    jest.isolateModules(() =>
      isolatedIncrementor = require('./incrementor').default
    )
    expect(isolatedIncrementor.getState()).toBe(1)

    isolatedIncrementor = require('./incrementor').default
    expect(isolatedIncrementor.getState()).toBe(2)
  })

  it('can isolate the same module multiple times', () => {
    let isolatedIncrementor

    jest.isolateModules(() =>
      isolatedIncrementor = require('./incrementor').default
    )
    isolatedIncrementor.increment()

    jest.isolateModules(() =>
      isolatedIncrementor = require('./incrementor').default
    )
    expect(isolatedIncrementor.getState()).toBe(1)
  })
})

@facebook-github-bot
Copy link
Contributor

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

@willym1 willym1 changed the title Fix isolateModules not working correctly when module has already been imported Fix isolateModules not working correctly when module has already been imported Jul 3, 2019
@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@codecov-io
Copy link

codecov-io commented Jul 3, 2019

Codecov Report

Merging #8634 into master will decrease coverage by <.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #8634      +/-   ##
==========================================
- Coverage    63.4%   63.39%   -0.01%     
==========================================
  Files         274      274              
  Lines       11342    11341       -1     
  Branches     2770     2769       -1     
==========================================
- Hits         7191     7190       -1     
  Misses       3534     3534              
  Partials      617      617
Impacted Files Coverage Δ
packages/jest-runtime/src/index.ts 68.96% <100%> (-0.09%) ⬇️

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 2b64bb4...1ae4569. Read the comment docs.

@SimenB SimenB requested a review from rogeliog July 3, 2019 06:07
@willym1
Copy link
Author

willym1 commented Jul 3, 2019

This only updates behavior with regular modules and not mocked modules. The field name _isolatedMockRegistry exists and there's similar code inside requireMock method but I left it alone since I'm not even sure what the use case would be for isolating mocked modules

@willym1 willym1 marked this pull request as ready for review July 3, 2019 06:27
Copy link
Collaborator

@thymikee thymikee left a comment

Choose a reason for hiding this comment

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

LGTM. Cc @rogeliog

@rogeliog
Copy link
Contributor

rogeliog commented Jul 4, 2019

This only updates behavior with regular modules and not mocked modules. The field name _isolatedMockRegistry exists and there's similar code inside requireMock method but I left it alone since I'm not even sure what the use case would be for isolating mocked modules

From what I understand the use case for this is for whenever you are requiring a module that is mocked, which could be stateful. In some cases that mock might have been declared outside of the test file. For example in a setupFilesAfterEnv or moduleNameMapper

) {
moduleRegistry = this._moduleRegistry;
} else {
if (this._isolatedModuleRegistry) {
Copy link
Contributor

@rogeliog rogeliog Jul 4, 2019

Choose a reason for hiding this comment

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

For the most part it looks good, but there is one thing that worries me. With this new implementation there is one change in behavior.

Let's say that I have module A which depends in module B. If I do . jest.isolateModules(() => A = require('./A');) it will now also use a different copy of "B" also_(as well as for the dependencies of B)_ I'm worried that this might not be the what the user expects, and might make it a bit hard to debug issues.

My example of A and B might be easier to understand with A being MyComponent and B being React.

thoughts? @SimenB @thymikee

Copy link
Author

Choose a reason for hiding this comment

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

Good point. I've updated the code to not affect dependencies within an isolated module

Copy link
Contributor

Choose a reason for hiding this comment

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

Hi!

Thanks for all the work. Sorry, what I meant with my comment was just to express that there would be a change in behavior, I’m not saying that one is right and the other isn’t.

@thymikee @SimenB @willym1 I think it is important define exactly how we want isolateModules to work before proceeding.

I see a couple of options:

  1. [Current behavior] When an isolateModules block gets executed, only modules that are not in the module registry yet will be isolated. This means that things that have already been required will use the existing “require”.
  2. When an isolateModules block gets executed, it isolates every single module that gets required.
  3. When an isolateModules block gets executed, only the required only the direct requires get isolated but not their internal dependencies.

Here are thoughts for the each option:

What are your thoughts? @thymikee @SimenB @willym1

Copy link
Collaborator

Choose a reason for hiding this comment

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

This means that things that have already been required will use the existing “require”.

This is not really documented, isn't it?

I think I lean towards 2, since I'd expect everything inside the callback to get its own copy. This is fairly fresh addition to Jest, so I think there won't be too much cruft around it, especially if we're fixing it for good reasons.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is not really documented, isn't it?

Correct, it is not documented.

Copy link
Author

@willym1 willym1 Jul 11, 2019

Choose a reason for hiding this comment

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

Leaning towards 2 as well. 1 doesn't seem functionally intuitive. If you needed to do multiple tests on a module that requires each one to be a fresh copy, you'd have to individually isolate each module at the start of the test and know beforehand how many copies are needed.

1 and 3 suffers from another issue - since isolateModules calls aren't hoisted like a regular mock call would (correct me on this if I'm wrong), there's no guarantee that the internal dependencies of those copies were already previously required. Say you were isolating a module multiple times whose behavior is tied to some stateful internal dependencies. Any copy of the isolated module can indirectly affect the other copies simply by running, which may be problematic if you're trying to do multiple isolated tests. This is just a very specific case btw, I don't see this significantly impacting anyone currently using isolatedModules in their tests.

Copy link
Contributor

Choose a reason for hiding this comment

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

@rogeliog In my opinion, option 2 is the most intuitive, and the only behaviour that matches the name of the function. This is exactly what I thought isolatedModules did when I read the documentation, especially after having just read the documentation for resetModules() which is right above it, and the explicit mention of the word "sandbox".

This is just a very specific case btw, I don't see this significantly impacting anyone currently using isolatedModules in their tests.

@willym1 I found this issue+PR because this is something I'm currently struggling with. Because of this bug (and because of the lack of async import support: #10428) I've had to use resetModules() instead.

@willym1 willym1 force-pushed the isolateModules-fix branch 2 times, most recently from 15d7d5d to 96a19a1 Compare July 5, 2019 10:36
@willym1
Copy link
Author

willym1 commented Jul 5, 2019

Made some revisions addressing @rogeliog's comments:

  1. Accommodate isolating mocked modules
  2. Dependencies being imported within an isolated module won't be indirectly isolated. This is accomplished by adding a new boolean field _isolatedRequireInProgress to the Runtime class. Its purpose is to keep track of the current isolated module being loaded. If the value is set to true, avoid isolating any modules. A local variable disableIsolatedRequireInProgress within requireModule and requireMock signal when to begin isolating again.

(also idk what happened here, a bunch of checks just failed on a single test in jest-util. I'm without context but it seems unrelated to my changes)

@SimenB
Copy link
Member

SimenB commented Jul 5, 2019

Not sure about CI... Master just passed, would you mind rebasing. Maybe circle was weird for a moment?

@willym1
Copy link
Author

willym1 commented Jul 5, 2019

Seems like the test failure's reproducible with my code. Ran the specific test locally and passed. Not sure what to do about this one 😕

image

@willym1
Copy link
Author

willym1 commented Jul 7, 2019

@rogeliog Ready for re-review

@SimenB
Copy link
Member

SimenB commented Nov 9, 2019

@willym1 could you rebase this, please?

@helielson
Copy link

Any updates here? @willym1 do you need any help?

@Ranguna
Copy link

Ranguna commented May 1, 2020

No updates since November. What's going on here ?

@SimenB
Copy link
Member

SimenB commented May 1, 2020

Just waiting for a rebase, I think it's good to land? Maybe @rogeliog wants to give it another lookover?

@willym1
Copy link
Author

willym1 commented May 1, 2020

Hi - sorry this had been sitting for a while and I've forgotten about it. Last time I checked it seems there was still some unresolved implementation discussion.

Will hop on this PR again this weekend and see whether there's additional changes needed on my latest update and try to merge if everything's good to go.

@Ranguna
Copy link

Ranguna commented May 2, 2020

Good to see that you're all still working on this.

Thank you for all the good work 👍

@SimenB
Copy link
Member

SimenB commented May 5, 2020

@willym1 seems like this broke a test in packages/jest-regex-util, could you take a look? Whatever the regression there was, we should add a dedicated test for it

@willym1
Copy link
Author

willym1 commented May 5, 2020

Yeah there's an issue with how isolated modules deals with mocks. Will investigate some more

@SimenB
Copy link
Member

SimenB commented Oct 19, 2020

@willym1 were you able to look more into this?

@github-actions
Copy link

github-actions bot commented Sep 8, 2022

This PR is stale because it has been open 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Sep 8, 2022
@Ranguna
Copy link

Ranguna commented Sep 9, 2022

I'm unsure if this has been fixed by another PR, but I'll just comment here to prevent bot supremacy.

@github-actions github-actions bot removed the Stale label Sep 9, 2022
@SimenB
Copy link
Member

SimenB commented Sep 9, 2022

I've merged in main and resolved the conflicts. That said, I don't know the state of this. @willym1 are you still up for finishing this? 🙂

@SimenB
Copy link
Member

SimenB commented Sep 9, 2022

#10963 seems to have addressed the same issue? The test added in this PR doesn;t pass on master however, while the test added in that PR still passes. Not sure if the behaviour here is more correct, tho 😅

ANd regardless, we need to fix the error in the regex tests

@github-actions
Copy link

github-actions bot commented Sep 9, 2023

This PR is stale because it has been open 1 year with no activity. Remove stale label or comment or this will be closed in 30 days.

@github-actions github-actions bot added the Stale label Sep 9, 2023
@github-actions
Copy link

github-actions bot commented Oct 9, 2023

This PR was closed because it has been stalled for 30 days with no activity. Please open a new PR if the issue is still relevant, linking to this one.

@github-actions github-actions bot closed this Oct 9, 2023
Copy link

github-actions bot commented Nov 9, 2023

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 Nov 9, 2023
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.

9 participants