-
Notifications
You must be signed in to change notification settings - Fork 241
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
Remove side effects from stryker plugins #667
Comments
This is actually also the reason that people cannot simply use |
How would Stryker know what is exported? What does require return? |
In order to understand exactly what happens in NodeJS land, it makes sense to look at how node modules are loaded. The text of a javascript file being (function(exports, require, module, __filename, __dirname) {
// Module code actually lives in here
}); Both const module = { exports: {} };
const { require, filename, dirname } = /*...*/;
((exports, require, module, __filename, __dirname) => {
// Module code actually lives in here
})(module.exports, require, module, filename, dirname); So The JS value that is on Back to the proposition of this issue. Doing this in typescript: // index.ts (FooTranspiler project)
export { FooTranspiler } from './FooTranspiler'; Translates to this javascript: // index.js (FooTranspiler project)
var FooTranspiler_1 = require("./FooTranspiler");
exports.FooTranspiler = FooTranspiler_1.FooTranspiler; Which in turn means that const plugin = require('./FooTranspiler');
plugin.FooTranspiler // <== this is the FooTranspiler. Other ideas may include a karma like model: // From: https://github.com/karma-runner/karma-mocha/blob/master/lib/index.js
module.exports = {
'framework:mocha': ['factory', initMocha]
} This is really flexible, but also complicated. For karma it ties into there dependency injection mechanism. So in this example the array Or we can do something clever with the What will it be? |
Right, and because we can read the keys of the properties on the object that |
Exactly. Eliminating the side effects. This will allow us to just So, you're in? |
Let's do this! (After we squat some of the bugs we have right now 🐛) |
Wait! We have bugs? 😲 |
Unfortunately this issue got a bit more complicated with the introduction of the logging api (#954). As calling We could do this this by introducing a Personally, I'm now more gravitating towards a dependency injection framework to prevent the api's being cluttered with this stuff. @stryker-mutator/stryker-core what are your thoughts on this? A "dependency injection framework" sounds pretty heavy. We could create a very light implementation using Destructing assignments. It would look roughly like this: interface DependencyContainer { // this could reside in "stryker-api" somewhere
getLogger: LoggerFactory;
strykerOptions: StrykerOptions;
// add more stuff that can be injected
}
// Within a TestRunner plugin
class MyAwesomeTestRunner {
private readonly log: Logger;
constructor(settings: TestRunnerSettings, { getLogger }: DependencyContainer ) {
this.log = getLogger(MyAwesomeTestRunner.name);
}
}
// Within Stryker:
const dependencies: DependencyContainer = createDependencyContainer();
const settings = createTestRunnerSettings(); // Our normal test runner settings, unchanged
new TestRunner(settings, dependencies); After this, it should be fairly straightforward to remove all implementations from the stryker-api, leaving only pure Interfaces. We will never have problems again and can even remove stryker-api from the peerDependencies if we want. |
Side note: this will make it much easier to run Stryker on the Stryker modules themselves! As we can simply use local references to plugins. |
Constructor injection of dependencies seems like a good way to go. I don't really like the idea of an object that contains our dependencies and injecting that |
Well.. maybe. You mean, just use the one and only Using that settings object to also house our |
This PR adds dependency injection to Stryker, in preparation of removing the side effects from the way we now load plugins. Instead of registering yourself to the correct factory (for example, by calling `ReporterFactory.instance().register`), it relies on plugins exporting a property called `strykerPlugins`. Just to be clear: this PR does fix not the issue reported in #667. It only adds the dependency injection mechanism to Stryker. We still need to do PR's for each package (can go relatively fast) The content of this PR: 1. Add a dependency injection framework called `typed-inject` to the packages folder; A type safe dependency injection framework 1. Add a package `stryker-api/di` which contains dependency injection related interfaces to help plugins know what to (type-safely) inject 1. Add `typed-inject` as a dependency to the core Stryker package and use it. Reporter plugins are loaded via this new mechanism. 1. Alter the `PluginLoader` in order to enable dependency injection in all plugins. It is still backward compatible with the old way of loading plugins. 1. Add a package to contain generic test helpers called `@stryker-mutator/test-helpers`. This package contains a `testInjector` to help inject common stuff into injectables for testability purposes. 1. It updated the `BroadcastReporter` and all build-in reporters to now use the new dependency injection mechanism.
Right now it is quite difficult to to tryout 1 plugin by linking it in your project (
npm i file:./path/to/plugin
). This is because plugins work based on global side effects.For example:
foo-transpiler
. The index.js file would look like:The problem is that the instance of
require('stryker-api/transpile');
is different in the linked package. This is by design (standard nodejs require functionality).We can solve this issue by not relying on side effects. Instead plugins would export:
@simondel what do you think?
The magic will be the postfix. This could be one of these:
ConfigEditor
,Transpiler
,Runner
,Framework
,Mutator
.If a plugin uses one of the factories we should create a deprecation warning. We can remove the factories in the 1.0 release.
UPDATE:
Lot of work is done. Still todo:
getLogger
to DIprocess.emitWarning
): https://nodejs.org/dist/latest-v11.x/docs/api/process.html#process_process_emitwarning_warning_type_code_ctorconfig
via DI (usingprocess.emitWarning
): https://nodejs.org/dist/latest-v11.x/docs/api/process.html#process_process_emitwarning_warning_type_code_ctorThe text was updated successfully, but these errors were encountered: