1 | import { MutantResult, MutantStatus, MutantRunPlan, MutantTestPlan, PlanKind } from '@stryker-mutator/api/core';
|
2 | import { DryRunCompletedEvent, MutationTestingPlanReadyEvent, Reporter, RunTiming } from '@stryker-mutator/api/report';
|
3 |
|
4 | import { Timer } from '../utils/timer.js';
|
5 |
|
6 | export abstract class ProgressKeeper implements Reporter {
|
7 | private timer!: Timer;
|
8 | private timing!: RunTiming;
|
9 | private ticksByMutantId!: Map<string, number>;
|
10 | protected progress = {
|
11 | survived: 0,
|
12 | timedOut: 0,
|
13 | tested: 0,
|
14 | mutants: 0,
|
15 | total: 0,
|
16 | ticks: 0,
|
17 | };
|
18 |
|
19 | public onDryRunCompleted({ timing }: DryRunCompletedEvent): void {
|
20 | this.timing = timing;
|
21 | }
|
22 |
|
23 | |
24 |
|
25 |
|
26 |
|
27 | public onMutationTestingPlanReady({ mutantPlans }: MutationTestingPlanReadyEvent): void {
|
28 | this.timer = new Timer();
|
29 | this.ticksByMutantId = new Map(
|
30 | mutantPlans.filter(isRunPlan).map(({ netTime, mutant, runOptions }) => {
|
31 | let ticks = netTime;
|
32 | if (runOptions.reloadEnvironment) {
|
33 | ticks += this.timing.overhead;
|
34 | }
|
35 | return [mutant.id, ticks];
|
36 | })
|
37 | );
|
38 | this.progress.mutants = this.ticksByMutantId.size;
|
39 | this.progress.total = [...this.ticksByMutantId.values()].reduce((acc, n) => acc + n, 0);
|
40 | }
|
41 |
|
42 | public onMutantTested(result: MutantResult): number {
|
43 | const ticks = this.ticksByMutantId.get(result.id);
|
44 | if (ticks !== undefined) {
|
45 | this.progress.tested++;
|
46 | this.progress.ticks += this.ticksByMutantId.get(result.id) ?? 0;
|
47 | if (result.status === MutantStatus.Survived) {
|
48 | this.progress.survived++;
|
49 | }
|
50 | if (result.status === MutantStatus.Timeout) {
|
51 | this.progress.timedOut++;
|
52 | }
|
53 | }
|
54 | return ticks ?? 0;
|
55 | }
|
56 |
|
57 | protected getElapsedTime(): string {
|
58 | return this.formatTime(this.timer.elapsedSeconds());
|
59 | }
|
60 |
|
61 | protected getEtc(): string {
|
62 | const totalSecondsLeft = Math.floor((this.timer.elapsedSeconds() / this.progress.ticks) * (this.progress.total - this.progress.ticks));
|
63 |
|
64 | if (isFinite(totalSecondsLeft) && totalSecondsLeft > 0) {
|
65 | return this.formatTime(totalSecondsLeft);
|
66 | } else {
|
67 | return 'n/a';
|
68 | }
|
69 | }
|
70 |
|
71 | private formatTime(timeInSeconds: number) {
|
72 | const hours = Math.floor(timeInSeconds / 3600);
|
73 |
|
74 | const minutes = Math.floor((timeInSeconds % 3600) / 60);
|
75 |
|
76 | return hours > 0
|
77 | ? `~${hours}h ${minutes}m`
|
78 | : minutes > 0
|
79 | ? `~${minutes}m`
|
80 | : '<1m';
|
81 | }
|
82 | }
|
83 |
|
84 | function isRunPlan(mutantPlan: MutantTestPlan): mutantPlan is MutantRunPlan {
|
85 | return mutantPlan.plan === PlanKind.Run;
|
86 | }
|