-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
fix: begin running tests immediately instead of waiting for watcher, reload required files in watch mode #5409
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
base: main
Are you sure you want to change the base?
Conversation
|
@mark-wiemer is using Also, let me know if you have any opinions about if/how I should test this change. It would be nice if I could inject some kind of chokidar mock so I can control timing of the file events to be able to verify in a test that it runs before the ready event is emitted...but the only easy way I think of to do that would be to call things in |
lib/cli/watch-run.js
Outdated
| }); | ||
|
|
||
| (async () => { | ||
| if (!globalFixtureContext) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The earlier versions of Mocha using Chokidar 3 still waited until watcher.on('ready') to call mocha.runGlobalSetup(), but I'm not sure why. A quick look at the code isn't too revealing, but I see why we'd want to call this right away. I'm curious if we can add a test case that has a complex global setup to ensure things still work?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not exactly familiar with what that global setup is (loading required modules?) but I can try to figure out what goes into it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay Chokidar doesn't seem to specify whether 'change'/'unlink' events can get emitted before 'ready' if changes occur during its initial scan, but if so, it could cause the preexisting code here to start running tests before it's performed global setup.
Obviously the intention of the preexisting code is to wait for the 'ready' event to run everything for the first time (first global setup, then the first test run).
I've fixed this in my PR now, if it gets an event before the global fixture context has been created it won't schedule a rerun.
(I still need to write tests for this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jedwards1211 do we still need tests for this?
|
Marking as draft until tests are added |
f8a4eca to
368d7c0
Compare
|
@mark-wiemer I want to run some changes by you before I dive into them... I realized that Also...
|
|
Hey @jedwards1211, the current glob code is not ideal for sure for exactly the reasons you mentioned and probably more. The best discussion around this is #5379 and in short we don't want to touch this code until v12. We as maintainers don't have a ton of experience with glob matching and don't want to break anything, but we do want to keep Chokidar at v4. Mocha 11.2.1 used Chokidar v3, we upgraded to v4 in Mocha 11.2.2, but Chokidar v4 dropped glob support so we had to use our own. Learning more about globs is definitely a priority, but I'm still getting the ropes on the rest of Mocha as a new maintainer, so I won't have a ton of updates here. In short, feel free to update or change or even rewrite the glob handling. Just keep #5379 in mind as another alternative and add as many tests as you can to help me and other maintainers verify any changes :) |
|
Yeah I kind of thought about doing the same thing as that PR. One question is how to change Instead we would have to delete all |
|
I wouldn't worry about performance too much in the abstract. We had a concrete issue with #5374 that has effectively been resolved with #5379, and in practice I can't imagine folks are using many regexes for watch paths. If we get a bug report about perf, we can look into it, but as long as our basic stress tests pass, I'd prefer simpler changes first. On another note, @mochajs/maintenance-crew I'm thinking of marking this as "semver-major" given the complexity impacted and possibility of bugs. Since we want Mocha 12 to have minimal changes we'll probably hold this one for Mocha 13. (#5357) |
|
Okay I guess I'll eventually piggyback this on #5379 and blast the cache with the new filtering from that PR. Also, I'm not sure what to do about getting the launch time to compare file mtimes to. Date etc could get monkeypatched by test utils like Sinin fake timers... do you have any thoughts? I guess it won't get monkeypatched before we run global setup though. But I got an ESLint error about using Date, it must be for good reason. |
|
I'm not sure why our linter warns about using Date. Could you see if that's a recommended rule? There's a real chance that our linter is very out of date. We've had some discussions about it but haven't prioritized updates there for a while. |
|
@mark-wiemer looks like the rule was explicitly added to guard against exactly the danger of fake timers I was talking about: #237 (This issue is explicitly linked in your ESLint config) Note, it would probably be possible to grab a reference to the original As I was saying though, in this case I only need to get the launch date before running any global setup where userland code might monkeypatch |
|
Thanks for researching. The core concern still applies, but I think the workaround does too: just have |
|
👋 Just checking in @jedwards1211, are you waiting on us for anything? |
|
No, I'll pick this back up soon now that that related PR is merged |
f7b12be to
527741b
Compare
527741b to
f99c91e
Compare
|
@mark-wiemer okay, to test timing-sensitive cases, I used an IPC channel to send mock |
|
Hi @jedwards1211, moved this to 12.x instead of 12.0, would still love to merge it but just clarifying that it isn't necessary for us to release Mocha 12. Are you able to continue working on this? |
|
Yeah, if there's any way I could have permission to retrigger CI it would help me vet it done sooner |
|
One strategy I've done for situations like this is to fork the upstream repo and send a PR within that repo to itself. That way you don't have to wait on upstream things. |
|
Oh I forgot I could do that, thanks for the tip! |
|
@jedwards1211 we also have our test workflow set to |
50cefea to
f769f11
Compare
498b754 to
3718750
Compare
|
@jedwards1211 congrats on getting the tests passing!! I'll check this one out soon. @JoshuaKGoldberg I think we can put this in 12.0, what do you think? |
|
In CI within my fork I noticed a preexisting test flaking out once, only getting one of two expected test runs in the output. I think this is the brittleness of sleep timing based approaches. If you want I could rewrite more of the existing watch tests to wait for messages from the mocha process instead of sleeping to mitigate the flakiness (A few of my tests mock chokidar to control timing of even its ready event, but I would leave existing tests using real chokidar. I would just tell the mocha process to send messages when it receives chokidar events, finishes a test run, etc and make tests wait for those events instead of just sleeping.) |
|
Btw, the final change that fixed the tests was checking if any of the |
You're welcome to open a second PR for this if you'd like, but let's keep this PR to its current scope :) |
|
Just FYI, might be a few days before I have enough energy to dedicate time to reviewing this, it's a very sensitive set of changes |
|
Okay sounds good |
| debug("resolved required file %s to %s", mod, modpath); | ||
| } | ||
| const requiredModule = await requireOrImport(modpath); | ||
| const requiredModule = forceRequire |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing handleRequires feels like it can be split into its own small PR with tests, am I correct there? Curious to track details about what problem this solves (e.g. create an issue for this showing why the change is needed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
haha with the scale of this PR, breaking out these few lines won't change much. a brief comment to cover why forceRequire is necessary would be appreciated here :)
| */ | ||
| const createTempDir = async () => { | ||
| const dirpath = await fsP.mkdtemp(path.join(os.tmpdir(), "mocha-")); | ||
| const dirpath = await fsP.realpath( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate why realpath is needed here, maybe with a code comment?
| /** | ||
| * Waits for an `EventEmitter` to emit the given `event`. | ||
| * @param {object} [opts] | ||
| * @param {EventFilter} [opts.filter] - predicate that will filter which event payloads will resolve the Promise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can never be too clear!
| * @param {EventFilter} [opts.filter] - predicate that will filter which event payloads will resolve the Promise | |
| * @param {EventFilter} [opts.filter] - predicate that will filter which event payloads will resolve the Promise (defaults to `() => true`) |
| } | ||
|
|
||
| /** | ||
| * Waits for an `EventEmitter` to emit the given `event`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * Waits for an `EventEmitter` to emit the given `event`. | |
| * Returns a promise that resolves when the `emitter` | |
| * emits the `event` with properties matching the `filter` |
| /** | ||
| * Predicate for `gotEvent` that will filter which event payloads will resolve the Promise | ||
| * @callback EventFilter | ||
| * @param {...?} payload - the payload emitted for the event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function only ever takes one arg, right?
| * @param {...?} payload - the payload emitted for the event | |
| * @param {*} payload - the payload emitted for the event |
| "ignore": [ | ||
| "test/integration/fixtures/esm/type-module/test-that-imports-non-existing-module.fixture.js", | ||
| "test/integration/fixtures/options/watch/test-with-dependency.fixture.js", | ||
| "test/integration/fixtures/options/watch/test-with-dependency-and-delay.fixture.js", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| "test/integration/fixtures/options/watch/test-with-dependency-and-delay.fixture.js", | |
| // import is only resolvable while running, not on a clean checkout | |
| "test/integration/fixtures/options/watch/test-with-dependency-and-delay.fixture.js", |
| "test-node:only:globalBdd": "npm run -s test-node-run-only -- --ui bdd test/only/global/bdd.spec --no-parallel", | ||
| "test-node:only:globalQunit": "npm run -s test-node-run-only -- --ui qunit test/only/global/qunit.spec --no-parallel", | ||
| "test-node:only:globalTdd": "npm run -s test-node-run-only -- --ui tdd test/only/global/tdd.spec --no-parallel", | ||
| "test-node:only:integration:watch": "npm run -s test-node-run-only -- --no-parallel --timeout 10000 --slow 3750 \"test/integration/options/watch.spec.js\"", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you clarify why this new command is needed?
| @@ -0,0 +1,8 @@ | |||
| const dependency = require('./lib/dependency'); | |||
|
|
|||
| it('checks dependency', async () => { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| it('checks dependency', async () => { | |
| // sleep and then check for existing depencency.fixture.js | |
| it('checks dependency', async () => { |
| ); | ||
| } | ||
|
|
||
| it("works when watcher is ready before global setup finishes", async function () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's clean up these tests. I am thinking something like "before watcher is ready" becomes its own suite.
e.g. handles files added before watcher is ready with timestamp after start
becomes before watcher is ready > files added > handles timestamp after start
that should help us de-dupe boilerplate here as well.
Let me know if you have questions. Goal is to keep test names short, even if they're deeply nested. Then we can better review if all scenarios are covered.
| // ... and now that we've gotten a new module, we need to use it again due | ||
| // to `mocha.ui()` call | ||
| const newMocha = new Mocha(mocha.options); | ||
| const newMocha = new Mocha(Object.assign({}, mocha.options, plugins)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be the same, just rewritten in a way I find a bit more readable
| const newMocha = new Mocha(Object.assign({}, mocha.options, plugins)); | |
| const newMocha = new Mocha({ | |
| ...mocha.options, | |
| plugins | |
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it looks like this second suggestion is actually the same behavior, but I'm not sure why we're spreading ...plugins
| const newMocha = new Mocha(Object.assign({}, mocha.options, plugins)); | |
| const newMocha = new Mocha({ ...mocha.options, ...plugins }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's make sure we have a unit test that covers when plugins are present vs not present
mark-wiemer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very close, I promise! If we clean up tests I'll have much more confidence in this :)
PR Checklist
status: accepting prsOverview
Turn off
ignoreInitialand distinguish between initial'add'events and paths added after test startup. We can compare themtimeof the path to the test startup time to decide if the change should trigger a rerun.Also, this PR changes watch mode to reload
--required files between runs.