/
MutantTranspileScheduler.ts
85 lines (73 loc) · 3.11 KB
/
MutantTranspileScheduler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { Observable, range } from 'rxjs';
import TestableMutant from '../TestableMutant';
import { File } from '@stryker-mutator/api/core';
import SourceFile from '../SourceFile';
import { Transpiler } from '@stryker-mutator/api/transpile';
import { Disposable } from '@stryker-mutator/api/plugin';
import TranspiledMutant from '../TranspiledMutant';
import TranspileResult from './TranspileResult';
import { errorToString } from '@stryker-mutator/util';
import { tokens } from '@stryker-mutator/api/plugin';
import { coreTokens } from '../di';
import { from } from 'rxjs';
import { flatMap, zip } from 'rxjs/operators';
import { BehaviorSubject } from 'rxjs';
const INITIAL_CONCURRENCY = 100;
export class MutantTranspileScheduler implements Disposable {
private currentMutatedFile: SourceFile;
private readonly concurrencyTicket$ = new BehaviorSubject<number>(INITIAL_CONCURRENCY);
public static inject = tokens(coreTokens.transpiler, coreTokens.transpiledFiles);
/**
* Creates a mutant transpiler
*/
constructor(private readonly transpiler: Transpiler, private readonly unMutatedFiles: ReadonlyArray<File>) { }
public scheduleTranspileMutants(allMutants: ReadonlyArray<TestableMutant>): Observable<TranspiledMutant> {
return from(allMutants).pipe(
zip(this.concurrencyTicket$.pipe(
flatMap(n => range(0, n))
)),
flatMap(([mutant]) => this.transpileMutant(mutant), 1 /* IMPORTANT! Never transpile multiple mutants at once! */)
);
}
/**
* Schedule next mutant to be transpiled
*/
public readonly scheduleNext = () => {
this.concurrencyTicket$.next(1);
}
/**
* Dispose
*/
public dispose() {
this.concurrencyTicket$.complete();
}
private createTranspiledMutant(mutant: TestableMutant, transpileResult: TranspileResult) {
return new TranspiledMutant(mutant, transpileResult, someFilesChanged(this.unMutatedFiles));
function someFilesChanged(unMutatedFiles: ReadonlyArray<File>): boolean {
return transpileResult.outputFiles.some(file => fileChanged(file, unMutatedFiles));
}
function fileChanged(file: File, unMutatedFiles: ReadonlyArray<File>) {
if (unMutatedFiles) {
const unMutatedFile = unMutatedFiles.find(f => f.name === file.name);
return !unMutatedFile || unMutatedFile.textContent !== file.textContent;
} else {
return true;
}
}
}
private async transpileMutant(mutant: TestableMutant): Promise<TranspiledMutant> {
const filesToTranspile: File[] = [];
if (this.currentMutatedFile && this.currentMutatedFile.name !== mutant.fileName) {
filesToTranspile.push(this.currentMutatedFile.file);
}
this.currentMutatedFile = mutant.sourceFile;
const mutatedFile = new File(mutant.fileName, Buffer.from(mutant.mutatedCode));
filesToTranspile.push(mutatedFile);
try {
const transpiledFiles = await this.transpiler.transpile(filesToTranspile);
return this.createTranspiledMutant(mutant, { outputFiles: transpiledFiles, error: null });
} catch (error) {
return this.createTranspiledMutant(mutant, { outputFiles: [], error: errorToString(error) });
}
}
}