-
Notifications
You must be signed in to change notification settings - Fork 251
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
Implement mutation switching 👽🔀 #1514
Comments
This would require a major change in how mutation is done. I've discussed this face to face with @simondel and the easiest thing will be to build it next to the current mutation implementation and let people opt-in with an We'll be starting development on this next week probably. We'll update this issue along the way. |
I've been working on a PoC for a couple of weeks now and it seems to be working with some success! https://github.com/nicojs/mutation-switch-instrumenter#mutation-switch-instrumenter I'll be using that PoC to see if we can run tests on Angular/Vue/React projects with Karma/Mocha/Jest, etc. |
Sounds great @nicojs! If you want, you can use my BigMath library to check the performance of this solution :) |
Not so fast there, cowboy 🤠. I've updated the original issue text to reflect the current progress in research and thoughts. As you can see, still some work to be done before we can start implementing. |
I've started to work on an implementation plan (see original issue text) |
Hi @nicojs ! I'm impressed with your Mutation switching new approach. Looking forward for it! By the way I was thinking about another way of applying mutations in JS/TS - using debugger API. On the first glance the flow can be following:
Seems like such approach has it's own pros and cons.
Cons:
What do you think about it? |
That's interesting. It does seem seriously limited. I'm also not sure if it is truly easier to implement since you'll need to inspect the test runner process, calculate the breakpoint position back to the original source code, and than understand the code enough to know which value you want to debug. If you want to talk more about it, let's do that on slack: https://app.slack.com/client/TTHUR6NNP |
As for the karma mutation coverage reporting. This seems pretty trivial with Karma's plugin system. A small PoC: // stryker-karma-plugin.js
module.exports = {
'framework:stryker': ['factory', strykerFramework],
'middleware:stryker': ['value', strykerMiddleware],
'middleware:body-parser': ['value', require('body-parser').json()]
}
function strykerFramework (config) {
config.files.unshift({
pattern: require.resolve('./adapter.js'),
included: true,
watched: false,
served: true
});
}
strykerFramework.inject = ['config'];
function strykerMiddleware(request, _response, next) {
if (request.method === 'POST' && request.url === '/stryker-info') {
console.log('Received from stryker:')
console.log(request.body);
} else {
next();
}
}
// adapter.js
const originalComplete = window.__karma__.complete.bind(window.__karma__);
window.__karma__.complete = (...args) => {
fetch('/stryker-info', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
test: true
})
}).then(() => {
originalComplete(...args);
});
} Activate with: // karma.conf.js
module.exports = function(config){
config.set({
files: ['src/*.js', 'test/*.js'],
frameworks: [
'jasmine',
'stryker' // Activate the "stryker" framework
],
plugins: [
'karma-*',
require.resolve('./stryker-karma-plugin') // Add plugin
],
singleRun: true,
browsers: [
'Chrome'
],
middleware: [
'body-parser', // body-parser used to parse json
'stryker' // Activate the "stryker" middleware
]
})
} |
Yow! We've started work on this in epic/mutation-switching. Now first, let me get a coffee ☕ |
About the The files that are copied to the sandbox should still be transformed somehow. For example, we should add @simondel @hugo-vrijswijk thoughts? |
If TypeScript projects can be supported as easily as they can now, I'm all for it :) |
For anyone that wants to help. We're using the 4.0 milestone for this. |
Wow, looking forward for this ❤️ |
If anyone wants to give it a try, we've released a beta 🤗 I'm hoping to release v4 in a few weeks 🤐 |
Wow! I think you are my personal hero 🦸♂️. Mutation switching by itself will not have a significant impact on Jest projects unfortunately. You might have some performance improvement because Jest would be able to cache build results, but definitely not an order of magnitude. However, it does open up the way for coverage analysis in jest. Hot reload should theoretically also be possible with Jest, but not with their current public api alone. We'll be focussing on jest next, after we've released v4. |
I think I'll have to check it on my BigMath project soon :D |
I tried this with Prettier on an 8 vCPU cloud instance, see brodycj/prettier#42 but encountered some issues:
I think brodycj/prettier#42 should be enough to reproduce my issues, but please let me know if there is anything else I can do to help isolate them. |
@brodybits As far as I know |
@brodybits there could be some memory leak 🤔 |
This comment has been minimized.
This comment has been minimized.
For Prettier, the initial test run went much faster when switching from Jest to the command runner. But for some reason I am getting many test failures from the initial test run. I have a feeling that something is going very wrong with what the mutation switching does to the JavaScript and will try to investigate it another day. |
@brodybits I complement your bravery, running Stryker on such huge open source projects. But yes, why shouldn't we?
See #2400
See #2399 (this one should not be that hard, string literal mutator shouldn't mutate
See #2401 🎉 |
I've managed to get it working too, though I did encounter a few bugs. See #2398 and #2402 for details. Some background. I'm working on a .js project without Typescript, bundling, or other instrumentation on the source code. I'm using Mocha for my test runner. Things I noticed:
|
Thanks @nicojs. I think the other major thing with Prettier is the resource usage and long prep time I had encountered on a large cloud instance, as I listed in #2401 (comment). I did clean up my attempt in brodycj/prettier#42, in case it may help at all. I can try to test new beta releases from time to time, otherwise leaving it in your hands for now. Yeah what a monster mutating Prettier! P.S. I did minimize my long comment as "duplicate", since it is now covered by separate issues. |
@Lakitna thanks for giving the beta a spin!
This is probably because we don't generate mutants anymore that we couldn't place (i.e. babel doesn't allow invalid syntax). A good example is property keys: const foo = {
'bar': baz
} In this example, the string literal
Awesome! 🍺
Yes, during the refactoring of the concurrency (for the checker api), I've decided to reserve 1 core for the stryker main process by default. You can override it with
Hmm interesting. Let's take a closer look at your use case and the possible performance improvements with mutation switching:
I was planning to pickup hot reload after the 4.0 release, but with this performance impact in mind, maybe we should already implement it in the beta? What do you think? Just to be sure, you are running with |
That was actually a minor annoyance for me. A good decision as far as I'm concerned
I'll give it a run with
The way you phrased it makes me think that hot reload is not a breaking change, right? The thing is that it's worse for me, but a lot better for others in this thread. In fact, Stryker suddenly became feasible for bigger projects. Thinking about this makes me curious about what the impact is on a small project with transpiling. Something like a React/Angular/Vue/... front-end written in Typescript. If someone knows about an open-source project like this, I'll be glad to run it in 3 and 4.beta. In fact, I've been wondering about how Stryker scales in general. The relation between mutation count and runtime feels exponential-ish. Would you be up for including some benchmarks in the repo? I'm not sure how to set it up yet, but I have some ideas. If the only projects that are worse off are those like mine, then I think its a no-brainer to release asap. When It's not as clear, and it rarely is, you can also release 4 with the note in the changelog that it comes with a performance hit for smaller projects.
Yes, I am. |
Well ... it might break some test suites that don't clean up nicely between test runs. See #2413
That's the goal yes! 😊
Yes, please! Benchmarking would be awesome! I started trying out some of them in the Feel free to add more use cases there! A great example of a big project that might look like your use case is express. If someone wants to add, please make the PR against the current master branch, that way it will make it easy for us to test it in both versions. 🤘 |
Express would be an interesting one for sure. I've created an issue for it. Though I've been thinking even further. I mentioned the relation between mutation count and runtime, I think it's interesting to explore that. So I was thinking of benchmarks that can be run with a variable amount of mutations at regular intervals. This would provide us with a performance graph, rather than a single number. I feel like something like that could help tremendously with decision making. |
Intro
Mutation switching is a technique that can be used to improve the performance of mutation testing. It is used in Stryker.NET as well as Stryker4s. We believe this has a lot of benefits for JavaScript/TypeScript as well.
In short, with mutation switching all mutants will be placed into the source code at once. Only one mutant can be active at the same time. Here is a small example:
Potential performance improvements
Although JavaScript is not a compiled language, there is still huge performance improvements to be had.
return global.activeMutant === 1 ? a - b : (global__coverMutant__(1), a + b);
. The__coverMutant__
here is an example of how we can count coverage analysis. Mutants that aren't covered, don't need to be tested. If we add the context of which test is running at that point in time, we can only run the tests that cover a particular mutant. We currently have a very complex coverage analysis in place, which we can totally get rid of once we have this new test coverage analysis system in place. It will work for a lot more use cases, no matter which transpiler or bundler or even minified you use. However, we will need to change all test runners to report this new mutation coverage statistic. There is also complication here for static code, see hurdles.Mutation switch instrumenter PoC.
I've created a Proof of Concept of mutation switching. It works for a couple of our mutations and I'm confident we can add them all. It has a lot of benefits:
Hurdles
There are some hurdles to overcome.
Rethinking
Transpilers
We currently have a
Transpiler
API. It is responsible for transpiling the code for the initial test run, as well as transpiling each mutant. Stryker takes care to load the transpiler and keep it alive, so the transpiler can use in-memory--watch
functionality if it can.Examples of transpilers:
Specific transpiler plugins are no longer needed when using mutation switching. We only need to transpiler once (with all mutants in the code), so there is no need to painstakingly transpile each mutant. The amount of maintenance is simply not worth the effort to keep this transpiler API, if only to run it once.
Instead, we should allow a user to configure a "build command" (name pending). It can be
tsc -b
for example, orwebpack --configFile webpack.conf.js
. This build command is then executed inside the sandbox before the initial test run starts. This sandbox is reused for the mutant test runs.When copying files to the sandbox, we might need to change them slightly. Take this tsconfig file for example:
Simply picking this file up and dropping it in the sandbox won't work. Assuming the sandbox is copied 2 directories deep, we need to preprocess it to be:
The goal of a transpiler
Migration scenario
I think a small step-by-step migration is out of the question, as approximately half of our code will have to be rewritten, or completely scrapped. Our public API will also need to be changed in a couple of places too.
If we want to keep feature parity with the current features, we will at least need support for transpiling using babel, webpack, typescript, jest, ts-node, karma-webpack, mocha-webpack, vue cli, angular CLI and react scripts and test runner support for karma, jasmine, mocha, and jest (I think we can drop WCT).
A high-level overview of the new way of working of a mutation test run:
Investigation progress
There's a lot of stuff we need to figure out. We'll have to investigate how all features are possible. Note: there is a lot of stuff "nice to have" here, so we shouldn't block implementation.
// @ts-nocheck
(works from TS3.7)grep
Implementation plan
Implementation should be done in steps. To make it easier on ourselves, we should allow us to break parts of Stryker in the meantime. I suggest therefore to work in a feature branch. We'll be implementing mutation switching on that branch.
In the meantime, our master branch will keep getting security patches, but we want to keep the functional changes to a minimum.
Question: Do we want to keep Dependabot during this time? I think it is better to point Dependabot to the feature branch instead of master.
The plan:
Preparation
html-reporter
package (not used anymore)api
package (chore to maintain, without any actual benefit)@next
for long-lived feature branches.Implementation
@stryker-mutator/instrumenter
(name pending). That way we keep it out of the Stryker core and it will all be a bit more manageable. We might choose to allow a new "instrumenter" plugin later, for now, I would suggest to let Stryker have a full-fledged dependency on it!@stryker-mutator/core
mutator
from directory from@stryker-mutator/api
@stryker-mutator/javascript-mutator
@stryker-mutator/vue-mutator
@stryker-mutator/typescript
. Since we will create a new transpiler, we can remove this entire package.[x] Add a new transpiler called inno longer needed, since we're scrapping the entire transpiler api@stryker-mutator/typescript-transpiler
that uses this work around. It should ignore all type errors and work for both project references and plain projects.*
init(files: []): Promise<void>
It should be fed the primary files and to check for errors in the initial test run (no errors)
*
check(mutant: Mutant): Promise<MutantStatus>
It should be fed one mutant at a time that is than type checked, or filtered (see Specify lines to mutate #1980 )
@stryker-mutator/typescript-checker
(name pending)@stryker-mutator/babel-transpiler
// @ts-nocheck
and updatestsconfig.json
files.OptionsEditor
.// @ts-nocheck
at the top of each js/ts file.I'll be keeping this post up to date with our current progress.
The text was updated successfully, but these errors were encountered: