Path: blob/main/extensions/copilot/test/base/simulationContext.ts
13388 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*--------------------------------------------------------------------------------------------*/45import * as fs from 'fs';6import path from 'path';7import { ApiEmbeddingsIndex, IApiEmbeddingsIndex } from '../../src/extension/context/node/resolvers/extensionApi';8import { ConversationStore, IConversationStore } from '../../src/extension/conversationStore/node/conversationStore';9import { IIntentService, IntentService } from '../../src/extension/intents/node/intentService';10import { ITestGenInfoStorage, TestGenInfoStorage } from '../../src/extension/intents/node/testIntent/testInfoStorage';11import { ILinkifyService, LinkifyService } from '../../src/extension/linkify/common/linkifyService';12import { ChatMLFetcherImpl } from '../../src/extension/prompt/node/chatMLFetcher';13import { createExtensionUnitTestingServices, ISimulationModelConfig } from '../../src/extension/test/node/services';14import { AIEvaluationService, IAIEvaluationService } from '../../src/extension/testing/node/aiEvaluationService';15import { IChatMLFetcher } from '../../src/platform/chat/common/chatMLFetcher';16import { ChatFetchResponseType, ChatResponses } from '../../src/platform/chat/common/commonTypes';17import { IChunkingEndpointClient } from '../../src/platform/chunking/common/chunkingEndpointClient';18import { ChunkingEndpointClientImpl } from '../../src/platform/chunking/common/chunkingEndpointClientImpl';19import { INaiveChunkingService, NaiveChunkingService } from '../../src/platform/chunking/node/naiveChunkerService';20import { CHAT_MODEL, Config, ConfigKey, ExperimentBasedConfig, ExperimentBasedConfigType, globalConfigRegistry, IConfigurationService } from '../../src/platform/configuration/common/configurationService';21import { DefaultsOnlyConfigurationService } from '../../src/platform/configuration/common/defaultsOnlyConfigurationService';22import { InMemoryConfigurationService } from '../../src/platform/configuration/test/common/inMemoryConfigurationService';23import { IEmbeddingsComputer } from '../../src/platform/embeddings/common/embeddingsComputer';24import { RemoteEmbeddingsComputer } from '../../src/platform/embeddings/common/remoteEmbeddingsComputer';25import { ICombinedEmbeddingIndex, VSCodeCombinedIndexImpl } from '../../src/platform/embeddings/common/vscodeIndex';26import { IVSCodeExtensionContext } from '../../src/platform/extContext/common/extensionContext';27import { IGitExtensionService } from '../../src/platform/git/common/gitExtensionService';28import { NullGitExtensionService } from '../../src/platform/git/common/nullGitExtensionService';29import { ICompletionsFetchService } from '../../src/platform/nesFetch/common/completionsFetchService';30import { CompletionsFetchService } from '../../src/platform/nesFetch/node/completionsFetchServiceImpl';31import { IProjectTemplatesIndex, ProjectTemplatesIndex } from '../../src/platform/projectTemplatesIndex/common/projectTemplatesIndex';32import { IReleaseNotesService } from '../../src/platform/releaseNotes/common/releaseNotesService';33import { ReleaseNotesService } from '../../src/platform/releaseNotes/vscode/releaseNotesServiceImpl';34import { IDocsSearchClient } from '../../src/platform/remoteSearch/common/codeOrDocsSearchClient';35import { DocsSearchClient } from '../../src/platform/remoteSearch/node/codeOrDocsSearchClientImpl';36import { IReviewService } from '../../src/platform/review/common/reviewService';37import { constructGlobalStateMemento, MockExtensionContext } from '../../src/platform/test/node/extensionContext';38import { TestingServiceCollection } from '../../src/platform/test/node/services';39import { SimulationReviewService } from '../../src/platform/test/node/simulationWorkspaceServices';40import { NullTestProvider } from '../../src/platform/testing/common/nullTestProvider';41import { ITestProvider } from '../../src/platform/testing/common/testProvider';42import { ITokenizerProvider, TokenizerProvider } from '../../src/platform/tokenizer/node/tokenizer';43import { IGithubAvailableEmbeddingTypesService, MockGithubAvailableEmbeddingTypesService } from '../../src/platform/workspaceChunkSearch/common/githubAvailableEmbeddingTypes';44import { IWorkspaceChunkSearchService, WorkspaceChunkSearchService } from '../../src/platform/workspaceChunkSearch/node/workspaceChunkSearchService';45import { IWorkspaceFileIndex, WorkspaceFileIndex } from '../../src/platform/workspaceChunkSearch/node/workspaceFileIndex';46import { createServiceIdentifier } from '../../src/util/common/services';47import { SyncDescriptor } from '../../src/util/vs/platform/instantiation/common/descriptors';48import { IJSONOutputPrinter, NoopJSONOutputPrinter } from '../jsonOutputPrinter';49import { SIMULATION_FOLDER_NAME } from '../simulation/shared/sharedTypes';50import { ITestInformation, TestInformation } from '../simulation/testInformation';51import { CachedTestInfo, CachingChatMLFetcher, IChatMLCache } from './cachingChatMLFetcher';52import { CachingChunkingEndpointClient, ChunkingEndpointClientSQLiteCache } from './cachingChunksEndpointClient';53import { CachingCodeOrDocSearchClient, CodeOrDocSearchSQLiteCache } from './cachingCodeSearchClient';54import { CachingCompletionsFetchService } from './cachingCompletionsFetchService';55import { CachingEmbeddingsComputer } from './cachingEmbeddingsFetcher';56import { CachingResourceFetcher } from './cachingResourceFetcher';57import { ICompletionsCache } from './completionsCache';58import { EmbeddingsSQLiteCache } from './embeddingsCache';59import { TestingCacheSalts } from './salts';60import { ISimulationEndpointHealth, SimulationEndpointHealthImpl } from './simulationEndpointHealth';61import { SimulationCodeSearchChunkSearchService } from './simuliationWorkspaceChunkSearch';62import { FetchRequestCollector, SpyingChatMLFetcher } from './spyingChatMLFetcher';63import { SimulationTest } from './stest';64import { ChatModelThrottlingTaskLaunchers, ThrottlingLimits as SimulationThrottlingLimits, ThrottlingChatMLFetcher } from './throttlingChatMLFetcher';65import { ThrottlingCodeOrDocsSearchClient } from './throttlingCodeOrDocsSearchClient';6667const dotSimulationPath = path.join(__dirname, `../${SIMULATION_FOLDER_NAME}`);6869export enum CacheScope {70Embeddings = 'embeddings',71TSC = 'tsc',72Roslyn = 'roslyn',73ESLint = 'eslint',74Pylint = 'pylint',75Ruff = 'ruff',76Pyright = 'pyright',77Python = 'python',78Notebook = 'notebook',79DocSearch = 'docs-search',80CodeSearch = 'code-search',81CPP = 'cpp',82Chunks = 'chunks-endpoint',83}8485export const ICachingResourceFetcher = createServiceIdentifier<ICachingResourceFetcher>('ICachingResourceFetcher');8687export interface ICachingResourceFetcher {88invokeWithCache<I, R>(cacheScope: CacheScope, input: I, cacheSalt: string, inputCacheKey: string, fn: (input: I) => Promise<R>): Promise<R>;89}9091export enum CacheMode {92Disable = 'disable', // never use the cache, don't update the cache93Require = 'require', // always use the cache, and fail if it's not available94Default = 'default', // use cache of available, but don't require it95}9697export class NoFetchChatMLFetcher extends ChatMLFetcherImpl {98public override fetchMany(...args: any[]): Promise<ChatResponses> {99return Promise.resolve({100type: ChatFetchResponseType.Success,101usage: { completion_tokens: 0, prompt_tokens: 0, total_tokens: 0, prompt_tokens_details: { cached_tokens: 0 } },102value: ['--no-fetch option is provided to simulations -- using a fixed ChatML response'],103requestId: 'no-fetch-request-id',104serverRequestId: undefined,105resolvedModel: ''106});107}108}109110export function createSimulationChatModelThrottlingTaskLaunchers(boost: boolean): ChatModelThrottlingTaskLaunchers {111112const throttlingLimits: SimulationThrottlingLimits = {113[CHAT_MODEL.GPT41]: { limit: 3, type: 'RPS' },114[CHAT_MODEL.GPT4OPROXY]: { limit: 1, type: 'RPS' },115[CHAT_MODEL.EXPERIMENTAL]: { limit: 3, type: 'RPS' },116[CHAT_MODEL.GPT4OMINI]: { limit: 18, type: 'RPS' },117[CHAT_MODEL.CUSTOM_NES]: { limit: 5, type: 'RPS' },118[CHAT_MODEL.O3MINI]: { limit: 1, type: 'RPS' },119[CHAT_MODEL.CLAUDE_SONNET]: { limit: 3, type: 'RPS' },120[CHAT_MODEL.CLAUDE_37_SONNET]: { limit: 4, type: 'RPS' },121[CHAT_MODEL.O1]: { limit: 4, type: 'RPS' },122[CHAT_MODEL.O1MINI]: { limit: 5, type: 'RPM' },123[CHAT_MODEL.GEMINI_FLASH]: { limit: 20, type: 'RPM' },124[CHAT_MODEL.DEEPSEEK_CHAT]: { limit: 1, type: 'RPS' },125[CHAT_MODEL.XTAB_4O_MINI_FINETUNED]: { limit: 5, type: 'RPS' }126};127128if (boost) {129throttlingLimits[CHAT_MODEL.CLAUDE_SONNET] = { limit: 20, type: 'RPS' };130throttlingLimits[CHAT_MODEL.CLAUDE_37_SONNET] = { limit: 20, type: 'RPS' };131}132133return new ChatModelThrottlingTaskLaunchers(throttlingLimits);134}135136export interface SimulationServicesOptions {137chatModelThrottlingTaskLaunchers: ChatModelThrottlingTaskLaunchers;138isNoFetchModeEnabled: boolean;139languageModelCacheMode: CacheMode;140createChatMLCache?: (info: CurrentTestRunInfo) => IChatMLCache;141createNesFetchCache?: (info: CurrentTestRunInfo) => ICompletionsCache;142resourcesCacheMode: CacheMode;143disabledTools: Set<string>;144swebenchPrompt: boolean;145summarizeHistory: boolean;146useExperimentalCodeSearchService: boolean;147configs: Record<string, unknown> | undefined;148}149150export interface CurrentTestRunInfo {151/**152* Current test being run.153*/154test: SimulationTest;155156/**157* Each test is run `n` times. This specifies which run this is [0..n-1]158*/159testRunNumber: number;160161/**162* For each test run, we capture fetch requests made.163*/164fetchRequestCollector: FetchRequestCollector;165166/**167* Whether we're working in a real workspace and extension host.168*/169isInRealExtensionHost: boolean;170}171172/**173* Creates an accessor suitable for running tests.174* The `IChatMLFetcher` will use caching and the chat endpoint is configurable via the `chatModel` parameter.175* The `IEmbeddingsComputer` will use caching and the embeddings endpoint is configurable via the `embeddingsModel` parameter.176*/177export async function createSimulationAccessor(178modelConfig: ISimulationModelConfig,179opts: SimulationServicesOptions,180currentTestRunInfo: CurrentTestRunInfo181): Promise<TestingServiceCollection> {182const testingServiceCollection = createExtensionUnitTestingServices(undefined, currentTestRunInfo, modelConfig);183if (currentTestRunInfo.isInRealExtensionHost) {184const { addExtensionHostSimulationServices } = await import('./extHostContext/simulationExtHostContext');185await addExtensionHostSimulationServices(testingServiceCollection);186}187188testingServiceCollection.define(ITestInformation, new SyncDescriptor(TestInformation, [currentTestRunInfo.test]));189try {190const newLocal = new Map<string, any>(currentTestRunInfo.test.nonExtensionConfigurations);191192const configs = Object.entries(opts.configs ?? {}).map(([key, value]) => [lookupConfigKey(key), value] as const);193194testingServiceCollection.define(IConfigurationService, new SyncDescriptor(195InMemoryConfigurationService,196[197new DefaultsOnlyConfigurationService(),198new Map<ExperimentBasedConfig<ExperimentBasedConfigType> | Config<any>, unknown>([199[ConfigKey.UseProjectTemplates, false],200[ConfigKey.SummarizeAgentConversationHistory, opts.summarizeHistory],201...currentTestRunInfo.test.configurations?.map<[ExperimentBasedConfig<ExperimentBasedConfigType> | Config<any>, unknown]>(c => [c.key, c.value]) ?? [],202...configs,203]),204newLocal,205])206);207} catch (err) {208console.log(currentTestRunInfo.test.nonExtensionConfigurations);209console.error('Error in createSimulationAccessor', err);210console.error(currentTestRunInfo.test.fullName);211throw err;212}213214const globalStoragePath = path.join(dotSimulationPath, 'cache', 'global-storage');215const globalStatePath = path.join(dotSimulationPath, 'cache', 'global-state');216217testingServiceCollection.define(ISimulationEndpointHealth, new SyncDescriptor(SimulationEndpointHealthImpl));218testingServiceCollection.define(IJSONOutputPrinter, new SyncDescriptor(NoopJSONOutputPrinter));219testingServiceCollection.define(ICachingResourceFetcher, new SyncDescriptor(CachingResourceFetcher, [currentTestRunInfo, opts.resourcesCacheMode]));220testingServiceCollection.define(IVSCodeExtensionContext, new SyncDescriptor(MockExtensionContext, [globalStoragePath, constructGlobalStateMemento(globalStatePath)]));221testingServiceCollection.define(IIntentService, new SyncDescriptor(IntentService));222223testingServiceCollection.define(IAIEvaluationService, new SyncDescriptor(AIEvaluationService));224225const docsSearchClient = new SyncDescriptor(ThrottlingCodeOrDocsSearchClient, [new SyncDescriptor(DocsSearchClient)]);226testingServiceCollection.define(ITokenizerProvider, new SyncDescriptor(TokenizerProvider, [false]));227228const cacheTestInfo = new CachedTestInfo(currentTestRunInfo.test, currentTestRunInfo.testRunNumber);229230let chatMLFetcher: SyncDescriptor<IChatMLFetcher> =231opts.isNoFetchModeEnabled232? new SyncDescriptor(NoFetchChatMLFetcher)233: new SyncDescriptor(ThrottlingChatMLFetcher, [234new SyncDescriptor(ChatMLFetcherImpl),235opts.chatModelThrottlingTaskLaunchers236]);237if (opts.createChatMLCache) {238chatMLFetcher = new SyncDescriptor(CachingChatMLFetcher, [239chatMLFetcher,240opts.createChatMLCache(currentTestRunInfo),241cacheTestInfo,242{ endpointVersion: 'CAPI' },243opts.languageModelCacheMode ?? CacheMode.Default244]);245}246if (currentTestRunInfo.fetchRequestCollector) {247chatMLFetcher = new SyncDescriptor(SpyingChatMLFetcher, [currentTestRunInfo.fetchRequestCollector, chatMLFetcher]);248}249250testingServiceCollection.define(IChatMLFetcher, chatMLFetcher);251252if (opts.createNesFetchCache === undefined || cacheTestInfo === undefined) {253testingServiceCollection.define(ICompletionsFetchService, new SyncDescriptor(CompletionsFetchService));254} else {255testingServiceCollection.define(ICompletionsFetchService, new SyncDescriptor(256CachingCompletionsFetchService,257[258opts.createNesFetchCache(currentTestRunInfo),259cacheTestInfo,260opts.languageModelCacheMode ?? CacheMode.Default,261currentTestRunInfo.fetchRequestCollector,262opts.isNoFetchModeEnabled,263])264);265}266267if (opts.languageModelCacheMode === CacheMode.Disable) {268testingServiceCollection.define(IEmbeddingsComputer, new SyncDescriptor(RemoteEmbeddingsComputer));269testingServiceCollection.define(IDocsSearchClient, docsSearchClient);270testingServiceCollection.define(IChunkingEndpointClient, new SyncDescriptor(ChunkingEndpointClientImpl));271testingServiceCollection.define(ICombinedEmbeddingIndex, new SyncDescriptor(VSCodeCombinedIndexImpl, [/*useRemoteCache*/ true]));272testingServiceCollection.define(IApiEmbeddingsIndex, new SyncDescriptor(ApiEmbeddingsIndex, [/*useRemoteCache*/ true]));273testingServiceCollection.define(IProjectTemplatesIndex, new SyncDescriptor(ProjectTemplatesIndex, [/*useRemoteCache*/ true]));274} else {275const embeddingCache = new EmbeddingsSQLiteCache(TestingCacheSalts.embeddingsCacheSalt, currentTestRunInfo);276testingServiceCollection.define(IEmbeddingsComputer, new SyncDescriptor(CachingEmbeddingsComputer, [embeddingCache]));277278const codeOrDocSearchCache = new CodeOrDocSearchSQLiteCache(TestingCacheSalts.codeSearchCacheSalt, currentTestRunInfo);279const chunksEndpointCache = new ChunkingEndpointClientSQLiteCache(TestingCacheSalts.chunksEndpointCacheSalt, currentTestRunInfo);280testingServiceCollection.define(IDocsSearchClient, new SyncDescriptor(CachingCodeOrDocSearchClient, [docsSearchClient, codeOrDocSearchCache]));281testingServiceCollection.define(ICombinedEmbeddingIndex, new SyncDescriptor(VSCodeCombinedIndexImpl, [/*useRemoteCache*/ false]));282testingServiceCollection.define(IApiEmbeddingsIndex, new SyncDescriptor(ApiEmbeddingsIndex, [/*useRemoteCache*/ false]));283testingServiceCollection.define(IProjectTemplatesIndex, new SyncDescriptor(ProjectTemplatesIndex, [/*useRemoteCache*/ false]));284testingServiceCollection.define(IChunkingEndpointClient, new SyncDescriptor(CachingChunkingEndpointClient, [chunksEndpointCache]));285}286287testingServiceCollection.define(INaiveChunkingService, new SyncDescriptor(NaiveChunkingService));288testingServiceCollection.define(ILinkifyService, new SyncDescriptor(LinkifyService));289testingServiceCollection.define(ITestProvider, new SyncDescriptor(NullTestProvider));290testingServiceCollection.define(ITestGenInfoStorage, new SyncDescriptor(TestGenInfoStorage));291testingServiceCollection.define(IConversationStore, new SyncDescriptor(ConversationStore));292testingServiceCollection.define(IReviewService, new SyncDescriptor(SimulationReviewService));293testingServiceCollection.define(IGitExtensionService, new SyncDescriptor(NullGitExtensionService));294testingServiceCollection.define(IReleaseNotesService, new SyncDescriptor(ReleaseNotesService));295testingServiceCollection.define(IWorkspaceFileIndex, new SyncDescriptor(WorkspaceFileIndex));296testingServiceCollection.define(IGithubAvailableEmbeddingTypesService, new SyncDescriptor(MockGithubAvailableEmbeddingTypesService));297298if (opts.useExperimentalCodeSearchService) {299testingServiceCollection.define(IWorkspaceChunkSearchService, new SyncDescriptor(SimulationCodeSearchChunkSearchService, []));300} else {301testingServiceCollection.define(IWorkspaceChunkSearchService, new SyncDescriptor(WorkspaceChunkSearchService));302}303304return testingServiceCollection;305}306307function lookupConfigKey(key: string): ExperimentBasedConfig<ExperimentBasedConfigType> | Config<any> {308const config = globalConfigRegistry.configs.get(key);309if (!config) {310throw new Error(`Configuration '${key}' provided does not exist in product. Double check if the configuration key exists by using it in vscode settings.json.`);311}312return config;313}314315export function loadConfigFile(configFilePath: string): Record<string, unknown> {316const resolvedPath = path.isAbsolute(configFilePath) ? configFilePath : path.join(process.cwd(), configFilePath);317const contents = fs.readFileSync(resolvedPath, 'utf-8');318const configs = JSON.parse(contents) as Record<string, unknown>;319if (!configs || typeof configs !== 'object') {320throw new Error('Invalid configuration file: ' + configFilePath);321}322return configs;323}324325export async function applyConfigFile(configService: IConfigurationService, configs: Record<string, unknown>): Promise<void> {326for (const [key, value] of Object.entries(configs)) {327const configKey = lookupConfigKey(key);328await configService.setConfig(configKey, value);329}330}331332333