-
Notifications
You must be signed in to change notification settings - Fork 250
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
feat(coverage analysis): Support transpiled code #559
Conversation
First attempt implement coverage analysis while having one transpiler enabled. Removed the old "keepSourceMaps" boolean on transpiler options in favor of "produceSourceMaps". This allows for the source map resolving logic to be moved to stryker itself. *Stryker API* * Rename `keepSourceMaps` -> `produceSourceMaps` on `TranspilerOptions` * Remove `getMappedLocation` method from `Transpiler` * Remove `FileLocation` interface from the transpiler api (not needed anymore) * Add an id` field to `MutantResult` and `MatchedMutant`. This is used in the progress reporter for now to identify mutants without test coverage. *Stryker* * Add dependency to 'source-map'. * Add class `SourceMapper` responsible for mapping original code to transpiled code. * Use `SourceMapper` in the `MutantTestMatcher` in order to locate a mutant in the js output file. * Use the mutant id in the progress reporter to identify mutants without test coverage. Using `MutantResult.NoCoverage` for this is not enough anymore as mutants that are not matched to a test can now also have the status `TranspileError`. *Stryker TypeScript* * Remove old source-mapping logic from stryker-typescript, it was never used
@Archcry could you add babel source map production to this branch? You can use stryker-typescript as an example. |
* Rename `StatementLocation` > `StatementIndex` * Move location methods to its own class `LocationHelper` * Remove an if statement
@simondel what do you think about this solution: Delay printing the warning until after the mutant is transpiled. If the transpiled code results in the same code is the same as without the mutant (for example: mutating a typescript interface), we don't even run any tests, as we know for sure they will pass. We just mark the mutant as survived, never printing a warning. EDIT: This is what i have implemented in my latest commit. |
* Don't run mutant in a sandbox when the transpiled result is the same as the not mutated transpiled result * When the tests for a mutant could not be selected because the statement or function could not be found in the maps, delay reporting the warning untill we know for sure a mutant does not have an early result.
Running Stryker on Stryker now takes 6 minutes on my machine (max resources allocated). Saving about 10 minutes.
|
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.
Here is part one of the feedback. I mainly find the SourceMapper to be quite confusing
@@ -10,5 +10,5 @@ export default interface TranspilerOptions { | |||
* Indicates whether or not the source maps need to be kept. |
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.
This comment seems to be out of date
const resultFiles = filterNotEmpty(allFiles.map(file => { | ||
if (file.transpiled && isTypescriptFile(file)) { | ||
return outputFiles[file.name]; | ||
let moreOutput = true; |
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.
Perhaps isSingleOutput = false
would be nicer. (Note that this turns the implementation around and you will also have to reverse the if (moreOutput)
check and you have to swap the way you set the variable after emitting.
transpiled: sourceFile.transpiled, | ||
emit(fileDescriptor: FileDescriptor): EmitOutput { | ||
const emittedFiles = this.languageService.getEmitOutput(fileDescriptor.name).outputFiles; | ||
const jsFile = emittedFiles.find(file => file.name.endsWith('.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.
Is there a chance that this outputs '.jsx'
files?
@@ -133,4 +123,8 @@ describe('TypescriptTranspiler', () => { | |||
expect(result.outputFiles).lengthOf(0); | |||
}); | |||
}); | |||
|
|||
describe('transpile with source maps', () => { |
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.
Is this supposed to be empty?
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.
euhm... 😒
runResult.tests.forEach((testResult, id) => this.addTestResult(id, testResult)); | ||
public selectAllTests(runResult: RunResult, testSelectionResult: TestSelectionResult) { | ||
this.testSelectionResult = testSelectionResult; | ||
runResult.tests.forEach((testResult, id) => this.selectTest(id, testResult)); |
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 would be cleaner if we would swap the params around so they are consistent. Right now we swap the params around when we call selectTest
@@ -65,6 +65,9 @@ export default class Sandbox { | |||
|
|||
public async runMutant(transpiledMutant: TranspiledMutant): Promise<RunResult> { | |||
const mutantFiles = transpiledMutant.transpileResult.outputFiles; | |||
if (transpiledMutant.mutant.testSelectionResult === TestSelectionResult.Failed) { |
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't we check this in the MutantTestMatcher?
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.
And if we move this check to the MutantTestMatcher, we may be able to remove the TestSelectionResult all together.
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.
No. I added it in this PR. See my comment here: #559 (comment)
If you have a better idea, please let me know :/
SOURCE_MAP_URL_REGEX.lastIndex = 0; | ||
let currentMatch: RegExpExecArray | null; | ||
let lastMatch: RegExpExecArray | null = null; | ||
while (currentMatch = SOURCE_MAP_URL_REGEX.exec(transpiledFile.content)) { |
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.
What does this do?
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 retrieves the final sourceMappingURL comment in a given file. I've added a comment to it 👍
} | ||
|
||
private getSourceMapForFile(transpiledFile: TextFile) { | ||
const sourceMappingUrl = this.getSourceMapUrl(transpiledFile); |
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.
Is it an URL that we get?
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.
Yes. It is either a data url (data:application/json;base64,ABC...
) or an external url (main.js.map
).
I've renamed some methods:
getSourceMapFromTranspiledFiles
-> getExternalSourceMap
getSourceMapFromDataUrl
-> getInlineSourceMap
Is that more clear?
} | ||
} | ||
|
||
export class TranspiledSourceMapper extends SourceMapper { |
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.
This class is quite difficult to read for me. I have the feeling that I get sent all over the place and I have no idea why the code does what it does.
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've refactored a bunch of code and made it more object-oriented by adding a SourceMap
class (instead of a SourceMapInfo
interface). Hope this is a bit clearer.
@@ -55,45 +65,52 @@ export default class MutantTestMatcher { | |||
} | |||
|
|||
enrichWithCoveredTests(testableMutant: TestableMutant) { |
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.
This is only used privately (except for when testing)
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.
Good catch. Yes, it is public as a convenience for unit testing. Ideally I don't like to make these methods public, but it already was the case and I didn't feel like refactoring that part as well.
@simondel Thanks for your feedback! I think I've commented on/fixed all remarks. Could you please take another look. |
Support coverage analysis while having one transpiler enabled. Remove the old "keepSourceMaps" boolean on transpiler options in favor of "produceSourceMaps". This allows for the source map resolving logic to be moved to stryker itself, removing the need to implement it in every transpiler plugin.
TODO:
Enable source map for the stryker-babel-transpilerThrow an error if the stryker-babel-transpiler is asked to produce source maps (for now)Done:
Stryker API
keepSourceMaps
->produceSourceMaps
onTranspilerOptions
getMappedLocation
method fromTranspiler
FileLocation
interface from the transpiler api (not needed anymore)id
field toMutantResult
andMatchedMutant
. This is used in the progress reporter for now to identify mutants without test coverage.Stryker
SourceMapper
responsible for mapping original code to transpiled code.SourceMapper
in theMutantTestMatcher
in order to locate a mutant in the js output file.MutantResult.NoCoverage
for this is not enough anymore as mutants that are not matched to a test can now also have the statusTranspileError
.MutationTestExecutor
: Report a mutant as survived early if a mutant results in the same transpiled code (i.e. mutating a TypeScript interface)MutantTestMatcher
: Delay reporting a warning if the statement could not be found in the statement map. Instead mark the mutant asTestSelectionResult.Failed
and later log the warning if it is ran in theSandbox
. Don't report if an early result prevented the mutant from being tested.Stryker TypeScript
produceSourceMaps
transpiler optionStryker babel transpiler