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(JestTestRunner): run jest with --findRelatedTests #1235

Merged
merged 10 commits into from
Nov 29, 2018

Conversation

RobertBroersma
Copy link
Contributor

@RobertBroersma RobertBroersma commented Nov 8, 2018

Pass sandbox fileName to the test runner.

It's probably not the best code, but it works. It speeds up my stryker runs from around 2h45m to around 45m (70% faster!).

It would be great if someone could assist me in writing tests and giving me some pointers for cleaning up/refactoring my code, as the project is quite a lot to take in for me atm.

Cheers, let me know what you think!

PS. As I'm not entirely clear on how to run the project locally on my own code, I had to replace importModule(name); with importModule(__dirname + '../../../' + name); in PluginLoader.ts in order for stryker to be able to find its plugins. Not sure why.

Fixes #1226

Pass sandbox fileName to the test runner.
@ghost ghost added the 🔎 Needs review label Nov 8, 2018
@bartekleon
Copy link
Member

@RobertBroersma in packages/stryker-jest-runner/src/jestTestAdapters/JestPromiseTestAdapter.ts in line 22 there is unnecessary comma. You have to remove it in order to tests can pass.
And remember to use npm test or npm run lint-info before PR so other tests can pass (not blocked by linter :) )

@RobertBroersma
Copy link
Contributor Author

@kmdrGroch You are right!

I had some trouble running tests and checks locally, but I think I got it now. Will push some fixes soon.

Copy link
Member

@nicojs nicojs left a comment

Choose a reason for hiding this comment

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

Awesome to see you found you're way around our repository. I've already taken a look and it looks good. I've got 2 suggestions already.

@RobertBroersma
Copy link
Contributor Author

@nicojs Thanks for the feedback! I've processed your comments and expanded the test for Sandbox.ts.

Should we consider adding a configuration option for running with or without --findRelatedTests? I don't see any reason why you would run it without the flag, but maybe someone else does.

@nicojs
Copy link
Member

nicojs commented Nov 12, 2018

Should we consider adding a configuration option for running with or without --findRelatedTests? I don't see any reason why you would run it without the flag, but maybe someone else does.

Can you explain to me how the filtering works? If I have a fooTest.js file that tests foo.js, but foo.js requires bar.js and tests it as well implicitly. What would happen if I run --findRelatedTests on bar.js?

I have high doubts that it would work in 100% of the cases, because of the dynamic nature of JavaScript.

@RobertBroersma
Copy link
Contributor Author

Can you explain to me how the filtering works? If I have a fooTest.js file that tests foo.js, but foo.js requires bar.js and tests it as well implicitly. What would happen if I run --findRelatedTests on bar.js?

I have high doubts that it would work in 100% of the cases, because of the dynamic nature of JavaScript.

I've never questioned the working of it tbh, but I've went and looked it up. From a curious stackoverflow user: https://stackoverflow.com/questions/44066996/how-does-jest-findrelatedtests-work-under-the-hood

When running --findRelatedTests path/to/src-code.js, the first thing that happens is Jest creates an instance of an internal package, jest-resolve-dependencies. It's a pretty straightforward class with two methods: resolve and resolveInverse.

findRelatedTests calls resolveInverse on the paths you provided, looking up every source and test file that requires your file, in our example path/to/src-code.js. This lookup relies directly on some Jest configuration, specifically roots and/or rootDir, to help resolve paths.

If the found file is a test file, Jest runs it, simple enough. If the found file is a source file, call it found-file.js, then any test files that import found-file.js and the test files that import any of the source files that themselves import found-file.js will be run.

It's a clever implementation of, as the maintainers put it, a resolver of "transitive inverse dependencies". You can see for yourself in this while loop.

However there have been reports of it not working optimally, which have been recognized by the Jest team, e.g. jestjs/jest#6062

@nicojs
Copy link
Member

nicojs commented Nov 12, 2018

Thanks for figuring this out @RobertBroersma. With all side effects in mind, let's make it configurable. Would you mind making the change?

The default can be enabled in my opinion. That way, it is a nice performance boost out-of-the box. Can we call it jest.enableFindRelatedTests? Or do you have another proposal?

@RobertBroersma
Copy link
Contributor Author

Thanks for figuring this out @RobertBroersma. With all side effects in mind, let's make it configurable. Would you mind making the change?

The default can be enabled in my opinion. That way, it is a nice performance boost out-of-the box. Can we call it jest.enableFindRelatedTests? Or do you have another proposal?

No problem! I agree making it configurable would be good. I'll make the change. jest.enableFindRelatedTests sounds good to me.

@RobertBroersma
Copy link
Contributor Author

@nicojs I've made the flag configurable. Not sure if this is the best way or if and how I should add any tests for it. Let me know what you think!

Copy link
Member

@nicojs nicojs left a comment

Choose a reason for hiding this comment

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

Great, this PR is looking better and better. I've got some small remarks about naming and functionality location.

We should indeed add some tests in "JestPromiseTestAdapterSpec.ts" to see if the correct options are provided. Are you willing to add those as well?

We also shouldn't forget to document this feature in the readme.

@@ -15,19 +16,25 @@ export default class JestTestRunner implements TestRunner {
// Get jest configuration from stryker options and assign it to jestConfig
this.jestConfig = options.strykerOptions.jest.config;

// Get enableFindRelatedTests from stryker jest options or default to true
this.enableFindRelatedTests = options.strykerOptions.jest.enableFindRelatedTests;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can log on debug what we are doing with this setting?

public run(timeout: number, testHooks: string | undefined): Promise<RunResult> {
return this.testRunner.run({ timeout, testHooks });
public run(timeout: number, testHooks: string | undefined, mutant?: TestableMutant): Promise<RunResult> {
return this.testRunner.run({ timeout, testHooks, ...(mutant && { mutatedFileName: this.fileMap[mutant.fileName]}) });
Copy link
Member

Choose a reason for hiding this comment

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

Could we move the locating of the mutated file to the runMutant call? It is specific to the case of running when targeting a mutant. I feel like the separation of concerns is intermingled now.

@@ -1,5 +1,5 @@
import { RunResult } from 'jest';

export default interface JestTestAdapter {
run(config: object, projectRoot: string): Promise<RunResult>;
run(config: object, projectRoot: string, enableFindRelatedTests: boolean, mutatedFileName?: string): Promise<RunResult>;
Copy link
Member

Choose a reason for hiding this comment

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

Could we leave the name mutatedFileName out of this interface. It is not a clean separation of concerns. The JestTestAdapter shouldn't care about mutants, only about running tests. I think these 2 options can be combined into 1: fileNameUnderTest or something. If it is provided, it can add {findRelatedTests: true, _: [fileNameUnderTest].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I understand. Do you mean to only pass the fileNameUnderTest when running related to a specific file? I.e. when the flag is true and we're testing a mutant?

Copy link
Member

Choose a reason for hiding this comment

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

I.e. when the flag is true and we're testing a mutant?

Yeah that is what I mean. The JestTestAdapter should be an adapter for jest. No more, no less. It shouldn't make decisions.

jestConfig.reporters = [];
const config = JSON.stringify(jestConfig);
this.log.trace(`Invoking Jest with config ${config}`);
if (enableFindRelatedTests && mutatedFileName) {
this.log.trace(`Only running tests related to ${mutatedFileName}`);
Copy link
Member

Choose a reason for hiding this comment

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

I like this log message.

@RobertBroersma
Copy link
Contributor Author

I'll be working on your feedback hopefully soon. Got a bit busy at work the past days!

@nicojs
Copy link
Member

nicojs commented Nov 27, 2018

@RobertBroersma just let us know if you need some more help.

@RobertBroersma
Copy link
Contributor Author

@nicojs I'm hoping to find some time this evening. I'll let you know if I run into any issues.

@RobertBroersma
Copy link
Contributor Author

@nicojs I've processed your feedback. I think I understood most of it, let me know what you think!

I've added some unit tests. Was thinking about some integration tests, but couldn't quite figure them out yet. You think we should add some?

// basePath will be used in future releases of Stryker as a way to define the project root
// Default to process.cwd when basePath is not set for now, should be removed when issue is solved
// https://github.com/stryker-mutator/stryker/issues/650
this.jestConfig.rootDir = options.strykerOptions.basePath || process.cwd();
this.log.debug(`Project root is ${this.jestConfig.rootDir}`);
}

public async run(): Promise<RunResult> {
public async run(options?: RunOptions): Promise<RunResult> {
Copy link
Member

Choose a reason for hiding this comment

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

options here don't need a ?. It is always filled. No need for null/undefined checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I initially did that because tests started failing and I was unsure whether or not running without RunOptions was a case. I've taken a look at the other *TestRunnerSpecs and I've added some RunOptions to the Jest ones. Is this correct?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is great. We have RunnerOptions as options when creating a runner and RunOptions as options when calling the run() method. Confusing, I know 🙈

@@ -37,6 +38,18 @@ describe('JestPromiseTestAdapter', () => {
}, [projectRoot]));
});

it('should call the runCLI method with the --findRelatedTests flag', async () => {
Copy link
Member

Choose a reason for hiding this comment

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

Great test!

@nicojs
Copy link
Member

nicojs commented Nov 29, 2018

Thanks for the quick response! I first want to merge #1260. In there we have an integration test that tests the score of an actual stryker run with the jest runner. After that, I'll merge master in this branch and see if it works locally as expected (also will keep an eye on performance to see it improved).

After that, I'll merge. Should still be today.

Awesome job 👍

@ghost ghost assigned nicojs Nov 29, 2018
@nicojs
Copy link
Member

nicojs commented Nov 29, 2018

Worked great, although no performance improvement for the integration test (takes 1 minute and 1 second for both cases locally on my machine).

Will merge when green 👍

@nicojs nicojs merged commit 5e0790e into stryker-mutator:master Nov 29, 2018
@ghost ghost removed the 🔎 Needs review label Nov 29, 2018
@RobertBroersma
Copy link
Contributor Author

Thanks @nicojs !

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

Successfully merging this pull request may close these issues.

3 participants