Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/base/simulationBaseline.ts
13388 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import * as fs from 'fs';
6
import * as path from 'path';
7
import { IBaselineTestSummary } from '../simulation/shared/sharedTypes';
8
9
export class SimulationBaseline {
10
11
private prevBaseline = new Map<string, IBaselineTestSummary>();
12
private currBaseline = new Map<string, IBaselineTestSummary>();
13
private currSkipped = new Set<string>();
14
15
public get current(): IterableIterator<IBaselineTestSummary> {
16
return this.currBaseline.values();
17
}
18
19
public get currentScore(): number {
20
return this._computeScore(Array.from(this.currBaseline.values()));
21
}
22
23
public get overallScore(): number {
24
return this._computeScore(this.testSummaries);
25
}
26
27
private _computeScore(summaries: IBaselineTestSummary[]) {
28
const totalScore = summaries.reduce((acc, curr) => acc + curr.score, 0);
29
return (totalScore / summaries.length) * 100;
30
}
31
32
public static DEFAULT_BASELINE_PATH = path.join(__dirname, '../test/simulation', 'baseline.json');
33
34
public static async readFromDisk(baselinePath: string, runningAllTests: boolean): Promise<SimulationBaseline> {
35
let baselineFileContents = '[]';
36
try {
37
baselineFileContents = (await fs.promises.readFile(baselinePath)).toString();
38
} catch {
39
// No baseline file exists yet, create one
40
await fs.promises.writeFile(baselinePath, '[]');
41
}
42
const parsedBaseline = JSON.parse(baselineFileContents) as IBaselineTestSummary[];
43
return new SimulationBaseline(baselinePath, parsedBaseline, runningAllTests);
44
}
45
46
constructor(
47
public readonly baselinePath: string,
48
parsedBaseline: IBaselineTestSummary[],
49
private readonly _runningAllTests: boolean
50
) {
51
this.prevBaseline = new Map<string, IBaselineTestSummary>();
52
parsedBaseline.forEach(el => this.prevBaseline.set(el.name, el));
53
}
54
55
public setCurrentResult(testSummary: IBaselineTestSummary): TestBaselineComparison {
56
this.currBaseline.set(testSummary.name, testSummary);
57
const prevBaseline = this.prevBaseline.get(testSummary.name);
58
return (
59
prevBaseline
60
? new ExistingBaselineComparison(prevBaseline, testSummary)
61
: { isNew: true }
62
);
63
}
64
65
public setSkippedTest(name: string): void {
66
this.currSkipped.add(name);
67
}
68
69
public async writeToDisk(pathToWriteTo?: string): Promise<void> {
70
const path = pathToWriteTo ?? this.baselinePath;
71
await fs.promises.writeFile(path, JSON.stringify(this.testSummaries, undefined, 2));
72
}
73
74
/**
75
* Returns a sorted array of test summaries.
76
* This also includes skipped tests as this is meant to represent the baseline.json which would be written to disk.
77
*/
78
private get testSummaries(): IBaselineTestSummary[] {
79
const testSummaries = Array.from(this.currBaseline.values());
80
81
// Skipped tests remain in the baseline
82
for (const name of this.currSkipped) {
83
const prevBaseline = this.prevBaseline.get(name);
84
if (prevBaseline) {
85
testSummaries.push(prevBaseline);
86
}
87
}
88
89
if (!this._runningAllTests) {
90
// When running a subset of tests, we will copy over the old existing test results for tests that were not executed
91
const executedTests = new Set(testSummaries.map(el => el.name));
92
for (const testSummary of this.prevBaseline.values()) {
93
if (!executedTests.has(testSummary.name)) {
94
testSummaries.push(testSummary);
95
}
96
}
97
}
98
99
testSummaries.sort((a, b) => a.name.localeCompare(b.name));
100
return testSummaries;
101
}
102
103
public compare(): ICompleteBaselineComparison {
104
const prevMandatory = new Map<string, IBaselineTestSummary>();
105
const currMandatory = new Map<string, IBaselineTestSummary>();
106
const prevOptional = new Map<string, IBaselineTestSummary>();
107
const currOptional = new Map<string, IBaselineTestSummary>();
108
109
for (const [_, value] of this.prevBaseline) {
110
if (value.optional) {
111
prevOptional.set(value.name, value);
112
} else {
113
prevMandatory.set(value.name, value);
114
}
115
}
116
for (const [_, value] of this.currBaseline) {
117
if (value.optional) {
118
currOptional.set(value.name, value);
119
} else {
120
currMandatory.set(value.name, value);
121
}
122
}
123
const mandatory = SimulationBaseline.compare(prevMandatory, currMandatory, this.currSkipped);
124
const optional = SimulationBaseline.compare(prevOptional, currOptional, this.currSkipped);
125
return {
126
mandatory,
127
optional,
128
nUnchanged: mandatory.nUnchanged + optional.nUnchanged,
129
nImproved: mandatory.nImproved + optional.nImproved,
130
nWorsened: mandatory.nWorsened + optional.nWorsened,
131
addedScenarios: mandatory.addedScenarios + optional.addedScenarios,
132
removedScenarios: mandatory.removedScenarios + optional.removedScenarios,
133
skippedScenarios: mandatory.skippedScenarios + optional.skippedScenarios,
134
improvedScenarios: mandatory.improvedScenarios.concat(optional.improvedScenarios),
135
worsenedScenarios: mandatory.worsenedScenarios.concat(optional.worsenedScenarios)
136
};
137
}
138
139
private static compare(prevMap: Map<string, IBaselineTestSummary>, currMap: Map<string, IBaselineTestSummary>, currSkipped: Set<string>): IBaselineComparison {
140
let nUnchanged = 0;
141
let nImproved = 0;
142
let nWorsened = 0;
143
let addedScenarios = 0;
144
let removedScenarios = 0;
145
let skippedScenarios = 0;
146
const improvedScenarios: IModifiedScenario[] = [];
147
const worsenedScenarios: IModifiedScenario[] = [];
148
149
for (const [_, curr] of currMap) {
150
const prev = prevMap.get(curr.name);
151
if (prev) {
152
const comparison = new ExistingBaselineComparison(prev, curr);
153
if (comparison.isImproved) {
154
nImproved++;
155
improvedScenarios.push({ prevScore: prev.score, currScore: curr.score, name: curr.name });
156
} else if (comparison.isWorsened) {
157
nWorsened++;
158
worsenedScenarios.push({ prevScore: prev.score, currScore: curr.score, name: curr.name });
159
} else {
160
nUnchanged++;
161
}
162
} else {
163
addedScenarios++;
164
}
165
}
166
167
for (const [_, prev] of prevMap) {
168
if (!currMap.has(prev.name)) {
169
if (currSkipped.has(prev.name)) {
170
// this test is missing but it was skipped intentionally
171
skippedScenarios++;
172
} else {
173
removedScenarios++;
174
}
175
}
176
}
177
178
return { nUnchanged, nImproved, nWorsened, addedScenarios, removedScenarios, skippedScenarios, improvedScenarios, worsenedScenarios };
179
}
180
181
public clear() {
182
this.currBaseline.clear();
183
this.currSkipped.clear();
184
}
185
}
186
187
export interface IBaselineComparison {
188
nUnchanged: number;
189
nImproved: number;
190
nWorsened: number;
191
addedScenarios: number;
192
removedScenarios: number;
193
skippedScenarios: number;
194
improvedScenarios: IModifiedScenario[];
195
worsenedScenarios: IModifiedScenario[];
196
}
197
198
export interface IModifiedScenario {
199
name: string;
200
prevScore: number;
201
currScore: number;
202
}
203
204
export interface ICompleteBaselineComparison extends IBaselineComparison {
205
mandatory: IBaselineComparison;
206
optional: IBaselineComparison;
207
}
208
209
export type TestBaselineComparison = (
210
{ isNew: true }
211
| { isNew: false; isImproved: boolean; isWorsened: boolean; isUnchanged: boolean; prevScore: number; currScore: number }
212
);
213
214
export class ExistingBaselineComparison {
215
public readonly isNew = false;
216
public readonly isImproved: boolean;
217
public readonly isWorsened: boolean;
218
public readonly isUnchanged: boolean;
219
220
public readonly prevScore: number;
221
public readonly currScore: number;
222
223
constructor(
224
prev: IBaselineTestSummary,
225
curr: IBaselineTestSummary,
226
) {
227
this.prevScore = prev.score;
228
const prevN = prev.passCount + prev.failCount;
229
this.currScore = curr.score;
230
const currN = curr.passCount + curr.failCount;
231
232
const prevPassCount = Math.round(this.prevScore * prevN);
233
const currPassCount = Math.round(this.currScore * currN);
234
235
// Here we want to mark a change only if this is clearly a change also when the `prevN` would equal `currN`
236
let prevMinScore = this.prevScore;
237
let prevMaxScore = this.prevScore;
238
let currMinScore = this.currScore;
239
let currMaxScore = this.currScore;
240
241
if (prevN > currN) {
242
// We are now running less iterations than before
243
currMinScore = currPassCount / prevN;
244
currMaxScore = (currPassCount + (prevN - currN)) / prevN;
245
} else if (prevN < currN) {
246
// We are now running more iterations than before
247
prevMinScore = prevPassCount / currN;
248
prevMaxScore = (prevPassCount + (currN - prevN)) / currN;
249
}
250
251
if (currMinScore > prevMaxScore) {
252
this.isImproved = true;
253
this.isWorsened = false;
254
this.isUnchanged = false;
255
} else if (currMaxScore < prevMinScore) {
256
this.isImproved = false;
257
this.isWorsened = true;
258
this.isUnchanged = false;
259
} else {
260
this.isImproved = false;
261
this.isWorsened = false;
262
this.isUnchanged = true;
263
}
264
}
265
}
266
267