Path: blob/main/extensions/copilot/test/base/simulationOutcome.ts
13389 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import * as fs from 'fs';5import * as path from 'path';6import { SimpleRPC } from '../../src/extension/onboardDebug/node/copilotDebugWorker/rpc';7import { createServiceIdentifier } from '../../src/util/common/services';8import { ITestRunResult } from '../testExecutor';9import { SimulationTest, toDirname } from './stest';101112export const ISimulationOutcome = createServiceIdentifier<ISimulationOutcome>('ISimulationOutcome');1314export interface ISimulationOutcome {15readonly _serviceBrand: undefined;16get(test: SimulationTest): Promise<OutcomeEntry | undefined>;17set(test: SimulationTest, results: ITestRunResult[]): Promise<void>;18}1920type TestSubset = Pick<SimulationTest, 'outcomeCategory' | 'fullName'>;2122export class ProxiedSimulationOutcome implements ISimulationOutcome {2324declare readonly _serviceBrand: undefined;2526public static registerTo(instance: ISimulationOutcome, rpc: SimpleRPC): ISimulationOutcome {27rpc.registerMethod('ProxiedSimulationOutcome.get', (test) => instance.get(test));28rpc.registerMethod('ProxiedSimulationOutcome.set', ({ test, results }) => instance.set(test, results));29return instance;30}3132constructor(33private readonly rpc: SimpleRPC,34) {35}3637get(test: TestSubset): Promise<OutcomeEntry | undefined> {38return this.rpc.callMethod('ProxiedSimulationOutcome.get', { fullName: test.fullName, outcomeCategory: test.outcomeCategory } satisfies TestSubset);39}4041set(test: TestSubset, results: ITestRunResult[]): Promise<void> {42return this.rpc.callMethod('ProxiedSimulationOutcome.set', { test: { fullName: test.fullName, outcomeCategory: test.outcomeCategory } satisfies TestSubset, results });43}44}4546export const outcomePath = path.join(__dirname, '../test/outcome');4748export class SimulationOutcomeImpl implements ISimulationOutcome {4950declare readonly _serviceBrand: undefined;5152private readonly outcome: Map<string, OutcomeEntry[]> = new Map();5354constructor(55private readonly _runningAllTests: boolean56) {57}5859async get(test: TestSubset): Promise<OutcomeEntry | undefined> {60const filePath = path.join(outcomePath, this._getCategoryFilename(test.outcomeCategory));61const entriesBuffer = await fs.promises.readFile(filePath, 'utf8');62const entries = JSON.parse(entriesBuffer) as OutcomeEntry[];63return entries.find(entry => entry.name === test.fullName);64}6566set(test: TestSubset, results: ITestRunResult[]): Promise<void> {67const requestsSet = new Set<string>();68for (const testRun of results) {69for (const cacheInfo of testRun.cacheInfo) {70if (cacheInfo.type === 'request') {71requestsSet.add(cacheInfo.key);72}73}74}7576const requests = Array.from(requestsSet).sort();7778let entries: OutcomeEntry[];79if (!this.outcome.has(test.outcomeCategory)) {80entries = [];81this.outcome.set(test.outcomeCategory, entries);82} else {83entries = this.outcome.get(test.outcomeCategory)!;84}8586entries.push({87name: test.fullName,88requests89});9091return Promise.resolve();92}9394public async write(): Promise<void> {95for (const [category, entries] of this.outcome) {96// When running a subset of tests, we will copy over the old existing test results for tests that were not executed97const filePath = path.join(outcomePath, this._getCategoryFilename(category));9899if (!this._runningAllTests) {100let prevEntriesBuffer: string | undefined;101try {102prevEntriesBuffer = await fs.promises.readFile(filePath, 'utf8');103} catch (err) {104}105if (prevEntriesBuffer) {106try {107const prevEntries: OutcomeEntry[] = JSON.parse(prevEntriesBuffer);108const currentEntries = new Set<string>(entries.map(el => el.name));109for (const prevEntry of prevEntries) {110if (!currentEntries.has(prevEntry.name)) {111entries.push(prevEntry);112}113}114} catch (err) {115console.error(err);116}117}118}119120entries.sort((a, b) => a.name.localeCompare(b.name));121await fs.promises.writeFile(filePath, JSON.stringify(entries, undefined, '\t'));122}123// console.log(this.outcome);124}125126private _getCategoryFilename(category: string): string {127return `${toDirname(category.toLowerCase())}.json`;128}129130public async cleanFolder(): Promise<void> {131// Clean the outcome folder132const names = await fs.promises.readdir(outcomePath);133const entries = new Set(names.filter(name => name.endsWith('.json')));134for (const [category, _] of this.outcome) {135entries.delete(this._getCategoryFilename(category));136}137if (entries.size > 0) {138await Promise.all(139Array.from(entries.values()).map(entry => fs.promises.unlink(path.join(outcomePath, entry)))140);141}142}143}144145interface OutcomeEntry {146name: string;147requests: string[];148}149150151