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

tests: sequester mocks to -test.mocks.js files #14216

Closed
wants to merge 29 commits into from
Closed

Conversation

connorjclark
Copy link
Collaborator

@connorjclark connorjclark commented Jul 15, 2022

The purpose of this refactor is to avoid needing to think deeply about the module graph when mocking modules in a test. Since static imports are run first in ESM, there is no way to (within the same module) mock modules in a way that lets you access those mocked modules through the initial static imports (that's why the dynamic import mess exists right now). Instead, with this PR we define all the td.replaceEsm calls in a separate file, which our custom Mocha runner will import before loading the test file. If the test needs access to anything this mocks.js file sets up, it will be available via global.lighthouseTestContext.


Initially I explored a Mocha-centric solution:

Where the canonical test file name is the "wrapper", and hacks at Mocha's lifecycle via suite.beforeAll to setup test suites dynamically just in time. This would have allowed for the Mocha runner to be given multiple such tests (instead of the one-at-a-time method required currently) because suite.beforeAll runs just before Mocha runs the tests for that file. Do the setup in a normal root-scope beforeAll or inside a describe wouldn't work because tests added dynamically at that point are ignored by Mocha. Some drawbacks here:

  • it.only and --grep cease to work for these files
  • It really abuses Mocha's lifecycles
  • It's too tied to Mocha for my liking, and becomes a (potential) hurdle for any future attempt to change the test runner again (hopefully we won't need to)

@connorjclark connorjclark changed the title tests: sequester mocks to separate -test.mocks.js files tests: sequester mocks to -test.mocks.js files Jul 15, 2022
@connorjclark connorjclark changed the base branch from master to mocha-runner July 15, 2022 19:44
@@ -299,6 +300,11 @@ async function runMocha(tests, mochaArgs, invocationNumber) {
const rootHooksPath = mochaArgs.require || '../test-env/mocha-setup.js';
const {rootHooks} = await import(rootHooksPath);

let mocksFilePath;
if (tests.length === 1) {
Copy link
Collaborator Author

@connorjclark connorjclark Jul 15, 2022

Choose a reason for hiding this comment

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

Leaning on the definitions of "test isolation" above to indirectly control when these mocks files can be called. When isolation stuff is dropped (I'm working on it now), the runner can be rewritten slightly to instead run files by themselves if they have a mocks file defined. It wouldn't be a big change to do that in this PR but, I didn't think it was necessary, preferring a more incremental change.

@connorjclark connorjclark requested review from brendankenny and removed request for a team July 16, 2022 00:06
Base automatically changed from mocha-runner to master July 18, 2022 18:23
});
/** @type {import('./bin-test.mocks.js').TestContext} */
// @ts-expect-error
const testContext = global.lighthouseTestContext;
Copy link
Member

Choose a reason for hiding this comment

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

This seems like a roundabout way of doing import {testContext} from './bin-test.mocks.js' as the first import of the file. Could we do that instead?

Eslint could be updated to enforce this import order.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

No, because the import cache is refreshed after every call to td.replaceEsm, hence this whole setup.

Copy link
Collaborator Author

@connorjclark connorjclark Jul 18, 2022

Choose a reason for hiding this comment

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

Also I don't think order of static import evaluation is even specified in ESM.

Copy link
Collaborator Author

@connorjclark connorjclark Jul 18, 2022

Choose a reason for hiding this comment

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

^that's old knowledge. now, it's "source defined order" except for when there is async code in the root module scope, which all of these setup files have.

https://stackoverflow.com/questions/35551366/what-is-the-defined-execution-order-of-es6-imports

@connorjclark
Copy link
Collaborator Author

This is blocking #14202 and removal of "isolation" from the test runner.

@@ -171,6 +171,8 @@ const rawArgv = y
const argv =
/** @type {Awaited<typeof rawArgv> & CamelCasify<Awaited<typeof rawArgv>>} */ (rawArgv);

process.env.SNAPSHOT_UPDATE = argv.update ? '1' : '';
Copy link
Member

Choose a reason for hiding this comment

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

We're already doing this in main right?

@brendankenny
Copy link
Member

This is blocking #14202 and removal of "isolation" from the test runner.

What is the blocking part? I thought this was a reorganization for clarity of mocking/import order?

@connorjclark
Copy link
Collaborator Author

connorjclark commented Aug 9, 2022

For windows: #14202 (comment)

The errors seen in CI here are similar to what this PR tries to avoid. It could be the issue is elsewhere–I figured I'd wait for this to land and remove an extra source of issues before debugging further. Debugging potential issues being Windows....or quibble....or mocks.... was getting too much so I wanted to just solve one first.

For isolation: With this PR we can remove the manual listing of what test files to isolate, and instead either 1) isolate the files implicitly if they have an associated mocks file or 2) go further, and figure out how to run the mocks files just before a test module is loaded within a single Mocha node-api invocation.

@connorjclark
Copy link
Collaborator Author

I confirmed that this PR + #14202 fixes windows CI.

https://github.com/GoogleChrome/lighthouse/actions/runs/3316847458

@adamraine
Copy link
Member

Is it possible to unblock #14202, without this PR?

TBH I would prefer avoiding this solution if we can. It's pretty messy and I'm becoming less concerned about "thinking deeply about the module graph".

We could use dynamic imports with toplevel await to avoid the messy before callbacks and stuff. This would make it easy to convert a bunch of static imports into dynamic imports if something needs to be mocked. We wouldn't even need to think that deeply, we could just err on the side of dynamic imports when importing from core.

@connorjclark
Copy link
Collaborator Author

Please send a draft PR converting navigation-runner-test.js and snapshot-runner-test.js (and enabling those tests for windows CI job) so we can see what that looks like.

@connorjclark
Copy link
Collaborator Author

We are going with the approach in #14468 - see my comment there: #14468 (review)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants