Path: blob/main/extensions/copilot/src/extension/linkify/test/node/statCaching.spec.ts
13405 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 { expect, suite, test } from 'vitest';6import { NullEnvService } from '../../../../platform/env/common/nullEnvService';7import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';8import { CancellationToken } from '../../../../util/vs/base/common/cancellation';9import { LinkifyLocationAnchor } from '../../common/linkifiedText';10import { LinkifyService } from '../../common/linkifyService';11import { assertPartsEqual, createMockFsService, createMockWorkspaceService, workspaceFile } from './util';1213function createCountingFsService(listOfFiles: readonly string[]): { fs: IFileSystemService; statCallCount: () => number } {14const inner = createMockFsService(listOfFiles);15let callCount = 0;16const fs: IFileSystemService = {17...inner,18stat(uri) {19callCount++;20return inner.stat(uri);21},22};23return { fs, statCallCount: () => callCount };24}2526suite('Stat Caching - FilePathLinkifier', () => {2728test('Should cache stat calls for repeated file paths', async () => {29const { fs, statCallCount } = createCountingFsService(['file.ts']);30const workspaceService = createMockWorkspaceService();31const service = new LinkifyService(fs, workspaceService, NullEnvService.Instance);3233const linkifier = service.createLinkifier({ requestId: undefined, references: [] }, []);3435// First append: linkify `file.ts`36const r1 = await linkifier.append('`file.ts` ', CancellationToken.None);37const countAfterFirst = statCallCount();3839// Second append: linkify `file.ts` again — should reuse cached stat40const r2 = await linkifier.append('`file.ts` ', CancellationToken.None);41const countAfterSecond = statCallCount();4243// Both should produce file links44assertPartsEqual(r1.parts, [new LinkifyLocationAnchor(workspaceFile('file.ts')), ' ']);45assertPartsEqual(r2.parts, [new LinkifyLocationAnchor(workspaceFile('file.ts')), ' ']);4647// The second call should not have increased the stat count48expect(countAfterSecond).toBe(countAfterFirst);49});5051test('Should cache stat calls across different matches in same text', async () => {52const { fs, statCallCount } = createCountingFsService(['file.ts']);53const workspaceService = createMockWorkspaceService();54const service = new LinkifyService(fs, workspaceService, NullEnvService.Instance);5556const linkifier = service.createLinkifier({ requestId: undefined, references: [] }, []);5758// Two references to the same file in one chunk59await linkifier.append('`file.ts` and file.ts ', CancellationToken.None);60await linkifier.flush(CancellationToken.None);6162// The stat should be cached, so only 1 stat call for the same URI63expect(statCallCount()).toBe(1);64});6566test('Should not cache across different URIs', async () => {67const { fs, statCallCount } = createCountingFsService(['file.ts', 'other.ts']);68const workspaceService = createMockWorkspaceService();69const service = new LinkifyService(fs, workspaceService, NullEnvService.Instance);7071const linkifier = service.createLinkifier({ requestId: undefined, references: [] }, []);7273await linkifier.append('`file.ts` ', CancellationToken.None);74const countAfterFirst = statCallCount();7576await linkifier.append('`other.ts` ', CancellationToken.None);77const countAfterSecond = statCallCount();7879// Different files should each get their own stat call80expect(countAfterSecond).toBeGreaterThan(countAfterFirst);81});82});8384suite('Stat Caching - ModelFilePathLinkifier', () => {8586test('Should cache stat calls for repeated model file links', async () => {87const { fs, statCallCount } = createCountingFsService(['src/file.ts']);88const workspaceService = createMockWorkspaceService();89const service = new LinkifyService(fs, workspaceService, NullEnvService.Instance);9091const linkifier = service.createLinkifier({ requestId: undefined, references: [] }, []);9293// First link94await linkifier.append('[src/file.ts](src/file.ts) ', CancellationToken.None);95const countAfterFirst = statCallCount();9697// Same link again — should reuse cache98await linkifier.append('[src/file.ts](src/file.ts) ', CancellationToken.None);99const countAfterSecond = statCallCount();100101// Second should not add more stat calls102expect(countAfterSecond).toBe(countAfterFirst);103});104});105106suite('Stat Caching - Shared across linkifiers', () => {107108test('Should share stat cache between ModelFilePathLinkifier and FilePathLinkifier', async () => {109const { fs, statCallCount } = createCountingFsService(['src/file.ts']);110const workspaceService = createMockWorkspaceService();111const service = new LinkifyService(fs, workspaceService, NullEnvService.Instance);112113const linkifier = service.createLinkifier({ requestId: undefined, references: [] }, []);114115// ModelFilePathLinkifier processes this markdown link first116await linkifier.append('[src/file.ts](src/file.ts) ', CancellationToken.None);117const countAfterModel = statCallCount();118119// FilePathLinkifier processes same path as inline code — should reuse shared cache120await linkifier.append('`src/file.ts` ', CancellationToken.None);121const countAfterFile = statCallCount();122123// The shared cache should prevent additional stat calls124expect(countAfterFile).toBe(countAfterModel);125});126});127128129