-
Notifications
You must be signed in to change notification settings - Fork 242
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(typescript checker): group mutants to improve performance 🚀 (#3900)
Add grouping mutants to the `@stryker-mutator/typescript-checker` to improve performance. The typescript checker is slow because mutants are checked for compile errors one by one. Each time a mutant is checked, TypeScript will do full type-checking. This is sped up by running multiple checkers in parallel, using typescript's `--watch` mode, and only compiling in memory. Yet still, this is very slow, as a rule of thumb: 10x slower than not enabling this checker. This PR improves this by grouping mutants unrelated to each other in the same typescript compilation run. Any compilation errors that result from one of the mutants in a group can always be associated with one mutant. One downside to this approach is that a TypeScript compilation sometimes forgets to report some of the errors (although it is a design goal of TypeScript to report them all). For example microsoft/TypeScript#46272 This is why you disable this grouping behavior by adding: ```json { "typeScriptChecker": { "prioritizePerformanceOverAccuracy": false } } ```
- Loading branch information
1 parent
2f3f481
commit 2f4adaa
Showing
63 changed files
with
1,440 additions
and
292 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
{ | ||
"name": "typescript-project-references", | ||
"version": "0.0.0", | ||
"private": true, | ||
"description": "A module to perform an integration test", | ||
"main": "index.js", | ||
"scripts": { | ||
"clean": "rimraf dist", | ||
"prebuild": "npm run clean", | ||
"build": "tsc -b tsconfig.json", | ||
"pretest:unit": "npm run build", | ||
"test:unit": "mocha", | ||
"pretest": "rimraf \"reports\" \"dist\" \"stryker.log\"", | ||
"test": "stryker run", | ||
"posttest": "mocha --no-config --no-package --timeout 0 verify/verify.js" | ||
}, | ||
"mocha": { | ||
"spec": [ | ||
"test/**/*.js" | ||
] | ||
}, | ||
"author": "", | ||
"license": "ISC" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { count } from '../utils/math.js'; | ||
|
||
export function countArrayLength(todo: any[]): number { | ||
return count(todo); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { toUpperCase } from '../utils/text.js'; | ||
|
||
export function start(): string { | ||
const logText = "Starting job"; | ||
console.log(toUpperCase(logText)); | ||
return logText; | ||
} |
9 changes: 9 additions & 0 deletions
9
e2e/test/typescript-project-references/src/core/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"extends": "../../tsconfig.settings", | ||
"compilerOptions": { | ||
"outDir": "../dist/src" | ||
}, | ||
"references": [ | ||
{ "path": "../utils" } | ||
] | ||
} |
1 change: 1 addition & 0 deletions
1
e2e/test/typescript-project-references/src/core/tsconfig.tsbuildinfo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"program":{"fileNames":["../../../../node_modules/typescript/lib/lib.d.ts","../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./index.ts","./job.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"c9b6bdd48b8bdb8d8e7690c7cc18897a494b6ab17dc58083dacfaf14b846ab4f","signature":"40b6409b8d0dced1f6c3964012b7a7c1cd50e24c3242095d1c8cfc6cabe8bd31"},{"version":"e4c28c497fe6cc6364b113c181c32ba58e70f02d824295e72b15d9570b403104","signature":"9be66c79f48b4876970daed5167e069d7f12f1a1ca616ecaa0ca8280946344ca"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"strict":true,"target":1},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[1,3,2,5,4],"changeFileSet":[6,7],"latestChangedDtsFile":"./job.d.ts"},"version":"4.8.4"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function count(array: any[]) { | ||
return array.length; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function toUpperCase(text: string) { | ||
return text.toUpperCase(); | ||
} |
6 changes: 6 additions & 0 deletions
6
e2e/test/typescript-project-references/src/utils/tsconfig.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"extends": "../../tsconfig.settings", | ||
"compilerOptions": { | ||
"outDir": "../dist/utils", | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
e2e/test/typescript-project-references/src/utils/tsconfig.tsbuildinfo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"program":{"fileNames":["../../../../node_modules/typescript/lib/lib.d.ts","../../../../node_modules/typescript/lib/lib.es5.d.ts","../../../../node_modules/typescript/lib/lib.dom.d.ts","../../../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../../../node_modules/typescript/lib/lib.scripthost.d.ts","./math.ts","./text.ts"],"fileInfos":["2dc8c927c9c162a773c6bb3cdc4f3286c23f10eedc67414028f9cb5951610f60",{"version":"f20c05dbfe50a208301d2a1da37b9931bce0466eb5a1f4fe240971b4ecc82b67","affectsGlobalScope":true},{"version":"9b087de7268e4efc5f215347a62656663933d63c0b1d7b624913240367b999ea","affectsGlobalScope":true},{"version":"7fac8cb5fc820bc2a59ae11ef1c5b38d3832c6d0dfaec5acdb5569137d09a481","affectsGlobalScope":true},{"version":"097a57355ded99c68e6df1b738990448e0bf170e606707df5a7c0481ff2427cd","affectsGlobalScope":true},{"version":"6198e7d4a43aabb174a72ec9f0e8d2962912ad59ad90010aac3930868a8f62a4","signature":"0400cb85cef49e897c47df13e38b5cd199e0c900253f2d2ddf2e3491c27bc0a8"},{"version":"becd081df112726ab94c1ca1c05d6a59268fe0dabf7ad076d16ea851bf99e8fb","signature":"6039d94241358544e8d62a3a0ba90752a9973b3b2b422c187e2bcf7256fcda2e"}],"options":{"composite":true,"declaration":true,"declarationMap":true,"module":1,"noUnusedLocals":true,"noUnusedParameters":true,"strict":true,"target":1},"referencedMap":[],"exportedModulesMap":[],"semanticDiagnosticsPerFile":[6,7,1,3,2,5,4],"latestChangedDtsFile":"./text.d.ts"},"version":"4.8.4"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"$schema": "../../node_modules/@stryker-mutator/core/schema/stryker-schema.json", | ||
"packageManager": "npm", | ||
"disableTypeChecks": true, | ||
"testRunner": "mocha", | ||
"concurrency": 1, | ||
"coverageAnalysis": "perTest", | ||
"reporters": ["json", "html", "progress", "clear-text"], | ||
"checkers": ["typescript"], | ||
"tsconfigFile": "src/core/tsconfig.json", | ||
"fileLogLevel": "warn", | ||
"buildCommand": "npm run build", | ||
"plugins": [ | ||
"@stryker-mutator/mocha-runner", | ||
"@stryker-mutator/typescript-checker" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import { expect } from 'chai'; | ||
import {start} from '../src/core/job'; | ||
|
||
describe(start.name, () => { | ||
it('should format a correct message', () => { | ||
// Act | ||
const result = start(); | ||
|
||
// Assert | ||
expect(result).eq("Starting job"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"include": ["src/core", "src/utils", "test"] | ||
} |
17 changes: 17 additions & 0 deletions
17
e2e/test/typescript-project-references/tsconfig.settings.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
{ | ||
"compilerOptions": { | ||
"strict": true, | ||
"target": "es5", | ||
"moduleResolution": "node", | ||
"module": "commonjs", | ||
"composite": true, | ||
"declaration": true, | ||
"declarationMap": true, | ||
|
||
// These settings should be overridden by the typescript checker | ||
"noUnusedLocals": true, | ||
"noUnusedParameters": true, | ||
|
||
"types": [] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"type": "module" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { expectMetricsJsonToMatchSnapshot } from '../../../helpers.js'; | ||
|
||
describe('Verify stryker has ran correctly', () => { | ||
it('should report correct score', async () => { | ||
await expectMetricsJsonToMatchSnapshot(); | ||
}); | ||
}); |
21 changes: 21 additions & 0 deletions
21
e2e/test/typescript-project-references/verify/verify.js.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Verify stryker has ran correctly should report correct score 1`] = ` | ||
Object { | ||
"compileErrors": 3, | ||
"ignored": 0, | ||
"killed": 1, | ||
"mutationScore": 33.33333333333333, | ||
"mutationScoreBasedOnCoveredCode": 33.33333333333333, | ||
"noCoverage": 0, | ||
"runtimeErrors": 0, | ||
"survived": 2, | ||
"timeout": 0, | ||
"totalCovered": 3, | ||
"totalDetected": 1, | ||
"totalInvalid": 3, | ||
"totalMutants": 6, | ||
"totalUndetected": 2, | ||
"totalValid": 3, | ||
} | ||
`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
packages/typescript-checker/schema/typescript-checker-options.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"$schema": "http://json-schema.org/draft-07/schema", | ||
"title": "TypescriptCheckerPluginOptions", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"properties": { | ||
"typescriptChecker": { | ||
"description": "Configuration for @stryker-mutator/typescript-checker", | ||
"title": "TypescriptCheckerOptions", | ||
"additionalProperties": false, | ||
"type": "object", | ||
"default": {}, | ||
"properties": { | ||
"prioritizePerformanceOverAccuracy": { | ||
"description": "Configures the performance of the TypescriptChecker. Setting this to false results in a slower, but more accurate result.", | ||
"type": "boolean", | ||
"default": true | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { Mutant } from '@stryker-mutator/api/src/core/index.js'; | ||
|
||
import { toPosixFileName } from '../tsconfig-helpers.js'; | ||
|
||
import { TSFileNode } from './ts-file-node.js'; | ||
|
||
/** | ||
* To speed up the type-checking we want to check multiple mutants at once. | ||
* When multiple mutants in different files don't have overlap in affected files (or have small overlap), we can type-check them simultaneously. | ||
* These mutants who can be tested at the same time are called a group. | ||
* Therefore, the return type is an array of arrays, in other words: an array of groups. | ||
* | ||
* @param mutants All the mutants of the test project. | ||
* @param nodes A graph representation of the test project. | ||
* | ||
* @example | ||
* Let's assume we got the following dependencies in files of a project, and in every file is one mutant. | ||
* | ||
* ======== | ||
* = A.ts = | ||
* ======== | ||
* / \ | ||
* ======== ======== | ||
* = B.ts = = C.ts = | ||
* ======== ======== | ||
* \ | ||
* ======== | ||
* = D.ts = | ||
* ======== | ||
* | ||
* A imports B and C | ||
* C imports D | ||
* | ||
* In this example, we can type-check B and D simultaneously. | ||
* This is because these files can't throw errors in each other. | ||
* If we type check them, let's say B reports an error. | ||
* We know that the mutant in B created the type error. | ||
* If we type check B and D at the same time, it is possible that an error shows up in A. | ||
* When this happens, we go down the dependency graph and individually test the mutants in that group. | ||
* | ||
* In this function, we create groups of mutants who can be tested at the same time. | ||
*/ | ||
export function createGroups(mutants: Mutant[], nodes: Map<string, TSFileNode>): string[][] { | ||
const groups: string[][] = []; | ||
const mutantsToGroup = new Set(mutants); | ||
|
||
while (mutantsToGroup.size) { | ||
const group: string[] = []; | ||
const groupNodes = new Set<TSFileNode>(); | ||
const nodesToIgnore = new Set<TSFileNode>(); | ||
|
||
for (const currentMutant of mutantsToGroup) { | ||
const currentNode = findNode(currentMutant.fileName, nodes); | ||
if (!nodesToIgnore.has(currentNode) && !parentsHaveOverlapWith(currentNode, groupNodes)) { | ||
group.push(currentMutant.id); | ||
groupNodes.add(currentNode); | ||
mutantsToGroup.delete(currentMutant); | ||
addRangeOfNodesToSet(nodesToIgnore, currentNode.getAllParentReferencesIncludingSelf()); | ||
} | ||
} | ||
groups.push(group); | ||
} | ||
|
||
return groups; | ||
} | ||
|
||
function addRangeOfNodesToSet(nodes: Set<TSFileNode>, nodesToAdd: Iterable<TSFileNode>) { | ||
for (const parent of nodesToAdd) { | ||
nodes.add(parent); | ||
} | ||
} | ||
|
||
function findNode(fileName: string, nodes: Map<string, TSFileNode>) { | ||
const node = nodes.get(toPosixFileName(fileName)); | ||
if (node == null) { | ||
throw new Error(`Node not in graph: ${fileName}`); | ||
} | ||
return node; | ||
} | ||
|
||
function parentsHaveOverlapWith(currentNode: TSFileNode, groupNodes: Set<TSFileNode>) { | ||
for (const parentNode of currentNode.getAllParentReferencesIncludingSelf()) { | ||
if (groupNodes.has(parentNode)) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} |
Oops, something went wrong.