Loading src/common/language_server/language_client_wrapper.test.ts +14 −3 Original line number Diff line number Diff line Loading @@ -35,11 +35,13 @@ import { } from '../webview'; import { QUICK_CHAT_OPEN_TRIGGER } from '../quick_chat/constants'; import { TerminalManager } from '../duo_workflow/terminal_manager'; import { NoopObservabilityService } from '../observability'; import { LanguageClientWrapper, LanguageClientWrapperImpl } from './language_client_wrapper'; import { LanguageServerFeatureStateProvider } from './language_server_feature_state_provider'; import { FileSnapshotProvider } from './file_snapshot_provider'; import { GET_DIAGNOSTICS_REQUEST_METHOD } from './document_quality_handler'; import { RepositoryClient } from './repository_client'; import { LanguageServerStartupMonitor } from './language_server_startup_monitor'; jest.mock('../code_suggestions/gitlab_platform_manager_for_code_suggestions'); jest.mock('../log'); // disable logging in tests Loading Loading @@ -118,6 +120,15 @@ describe('LanguageClientWrapper', () => { ); }; const createWrapperWithMonitor = (options: Parameters<typeof createWrapper>[0] = {}) => { const wrapperWithMonitor = createWrapper(options); wrapperWithMonitor.startupMonitor = new LanguageServerStartupMonitor( options.mockTelemetryEnvironment ?? gitLabTelemetryEnvironment, new NoopObservabilityService(), ); return wrapperWithMonitor; }; beforeEach(() => { const gitLabPlatform: GitLabPlatformForAccount = gitlabPlatformForAccount; getGitLabPlatformMock.mockResolvedValue(gitLabPlatform); Loading Loading @@ -174,7 +185,7 @@ describe('LanguageClientWrapper', () => { describe('startup monitor', () => { it('resolves when the language server reaches Running state', async () => { // default mock fires State.Running immediately - just verify it resolves wrapper = createWrapper(); wrapper = createWrapperWithMonitor(); await expect(wrapper.initAndStart()).resolves.toBeUndefined(); }); Loading @@ -185,7 +196,7 @@ describe('LanguageClientWrapper', () => { ...client, onDidChangeState: jest.fn(() => ({ dispose: jest.fn() })), // never fires }); wrapper = createWrapper(); wrapper = createWrapperWithMonitor(); const promise = wrapper.initAndStart(); jest.advanceTimersByTime(10000); Loading @@ -204,7 +215,7 @@ describe('LanguageClientWrapper', () => { return { dispose: jest.fn() }; }), }); wrapper = createWrapper(); wrapper = createWrapperWithMonitor(); const promise = wrapper.initAndStart(); jest.advanceTimersByTime(30000); Loading src/common/language_server/language_client_wrapper.ts +3 −5 Original line number Diff line number Diff line Loading @@ -76,7 +76,7 @@ const createRequestFn = client.sendRequest(method, param); export interface LanguageClientWrapper { startupMonitor: LanguageServerStartupMonitor; startupMonitor: LanguageServerStartupMonitor | undefined; setCustomPlatformConfig(clientConfig: Partial<ClientConfig>): void; initAndStart(): Promise<void>; syncConfig(): Promise<void>; Loading Loading @@ -128,11 +128,10 @@ export class LanguageClientWrapperImpl implements LanguageClientWrapper { #platformClientConfig?: Partial<ClientConfig>; #startupMonitor: LanguageServerStartupMonitor; #startupMonitor: LanguageServerStartupMonitor | undefined; set startupMonitor(monitor: LanguageServerStartupMonitor) { this.#startupMonitor = monitor; this.#subscriptions.push(monitor); } constructor( Loading @@ -157,7 +156,6 @@ export class LanguageClientWrapperImpl implements LanguageClientWrapper { this.#documentQualityHandler = new DocumentQualityHandler(); this.#repositoryClient = repositoryClient; this.#subscriptions.push(this.#documentQualityHandler); this.#startupMonitor = new LanguageServerStartupMonitor(telemetryEnvironment); } setCustomPlatformConfig(clientConfig: Partial<ClientConfig>): void { Loading Loading @@ -257,7 +255,7 @@ export class LanguageClientWrapperImpl implements LanguageClientWrapper { this.#documentQualityHandler.getDiagnostics, ); const startupComplete = this.#startupMonitor.observe(this.#client); const startupComplete = this.#startupMonitor?.observe(this.#client); this.#client.start().catch(() => { // Rejection here is expected during crash/restart cycles - the monitor tracks the outcome }); Loading src/common/language_server/language_server_manager.test.ts +15 −8 Original line number Diff line number Diff line Loading @@ -30,11 +30,13 @@ import { LanguageClientWrapperImpl } from './language_client_wrapper'; import { LanguageServerManager } from './language_server_manager'; import { LanguageClientFactory } from './client_factory'; import { LanguageServerFeatureStateProvider } from './language_server_feature_state_provider'; import { LanguageServerStartupMonitor } from './language_server_startup_monitor'; jest.mock('../code_suggestions/code_suggestions_gutter_icon'); jest.mock('../code_suggestions/code_suggestions_status_bar_item'); jest.mock('../code_suggestions/code_suggestions_state_manager'); jest.mock('./language_client_wrapper'); jest.mock('./language_server_startup_monitor'); jest.mock('../code_suggestions/gitlab_platform_manager_for_code_suggestions'); jest.mock('../webview/setup_webviews'); jest.mock('../diagnostics/log_collector_output_channel'); Loading Loading @@ -64,9 +66,6 @@ describe('LanguageServerManager', () => { sendQuickChatMessageEvent: jest.fn(), syncConfig: jest.fn(), dispose: jest.fn(), startupMonitor: createFakePartial({ notifyHandshakeStarted: jest.fn(), }), }); clientContext = { ide: { Loading Loading @@ -95,6 +94,15 @@ describe('LanguageServerManager', () => { }); jest.mocked(LanguageClientWrapperImpl).mockReturnValue(clientWrapper); jest.mocked(GitLabPlatformManagerForCodeSuggestionsImpl).mockReturnValue(platformManager); jest.mocked(LanguageServerStartupMonitor).mockImplementation(() => { const monitor = createFakePartial<LanguageServerStartupMonitor>({ onDidChangePhase: jest.fn().mockReturnValue({ dispose: jest.fn() }), observe: jest.fn().mockResolvedValue(undefined), notifyHandshakeStarted: jest.fn(), dispose: jest.fn(), }); return monitor as unknown as LanguageServerStartupMonitor; }); client = createFakePartial<BaseLanguageClient>({ stop: jest.fn(), Loading Loading @@ -233,14 +241,13 @@ describe('LanguageServerManager', () => { it('calls notifyHandshakeStarted on the startupMonitor when initializationOptions is invoked', () => { const { initializationOptions } = jest.mocked(languageClientFactory.createLanguageClient).mock .calls[0][1]; const notifyHandshakeStarted = jest.spyOn( clientWrapper.startupMonitor, 'notifyHandshakeStarted', ); const mockResults = jest.mocked(LanguageServerStartupMonitor).mock.results; const monitorInstance = mockResults[mockResults.length - 1] .value as LanguageServerStartupMonitor; (initializationOptions as () => unknown)(); expect(notifyHandshakeStarted).toHaveBeenCalled(); expect(monitorInstance.notifyHandshakeStarted).toHaveBeenCalled(); }); it('initializes state manager', async () => { Loading src/common/language_server/language_server_manager.ts +7 −2 Original line number Diff line number Diff line Loading @@ -103,7 +103,8 @@ export class LanguageServerManager implements WebviewManager, VersionProvider { return; } const { gitLabPlatformManager, gitLabTelemetryEnvironment } = this.#dependencyContainer; const { gitLabPlatformManager, gitLabTelemetryEnvironment, observabilityService } = this.#dependencyContainer; const stateManager = new CodeSuggestionsStateManager( gitLabPlatformManager, this.#context, Loading @@ -112,7 +113,10 @@ export class LanguageServerManager implements WebviewManager, VersionProvider { const statusBarItem = new CodeSuggestionsStatusBarItem(stateManager); const gutterIcon = new CodeSuggestionsGutterIcon(this.#context, stateManager); const middleware = new LanguageClientMiddleware(stateManager); const startupMonitor = new LanguageServerStartupMonitor(gitLabTelemetryEnvironment); const startupMonitor = new LanguageServerStartupMonitor( gitLabTelemetryEnvironment, observabilityService, ); const startupStatusBarItem = new LanguageServerStartupStatusBarItem(startupMonitor); const baseAssetsUrl = vscode.Uri.joinPath( this.#context.extensionUri, Loading Loading @@ -162,6 +166,7 @@ export class LanguageServerManager implements WebviewManager, VersionProvider { await this.#wrapper.initAndStart(); const subscriptions = [ this.#wrapper, startupMonitor, startupStatusBarItem, outputChannel, vscode.commands.registerCommand( Loading src/common/language_server/language_server_startup_monitor.test.ts +5 −1 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import { createFakePartial } from '../test_utils/create_fake_partial'; import { log } from '../log'; import { Snowplow } from '../snowplow/snowplow'; import { GitLabTelemetryEnvironment } from '../platform/gitlab_telemetry_environment'; import { NoopObservabilityService } from '../observability'; import { LanguageServerStartupMonitor } from './language_server_startup_monitor'; jest.mock('../log'); Loading @@ -26,7 +27,10 @@ describe('LanguageServerStartupMonitor', () => { const telemetryEnvironment = createFakePartial<GitLabTelemetryEnvironment>({ getOs: jest.fn().mockReturnValue('linux'), }); monitor = new LanguageServerStartupMonitor(telemetryEnvironment); monitor = new LanguageServerStartupMonitor( telemetryEnvironment, new NoopObservabilityService(), ); client = createFakePartial<BaseLanguageClient>({ onDidChangeState: jest.fn(handler => { onDidChangeStateHandler = handler; Loading Loading
src/common/language_server/language_client_wrapper.test.ts +14 −3 Original line number Diff line number Diff line Loading @@ -35,11 +35,13 @@ import { } from '../webview'; import { QUICK_CHAT_OPEN_TRIGGER } from '../quick_chat/constants'; import { TerminalManager } from '../duo_workflow/terminal_manager'; import { NoopObservabilityService } from '../observability'; import { LanguageClientWrapper, LanguageClientWrapperImpl } from './language_client_wrapper'; import { LanguageServerFeatureStateProvider } from './language_server_feature_state_provider'; import { FileSnapshotProvider } from './file_snapshot_provider'; import { GET_DIAGNOSTICS_REQUEST_METHOD } from './document_quality_handler'; import { RepositoryClient } from './repository_client'; import { LanguageServerStartupMonitor } from './language_server_startup_monitor'; jest.mock('../code_suggestions/gitlab_platform_manager_for_code_suggestions'); jest.mock('../log'); // disable logging in tests Loading Loading @@ -118,6 +120,15 @@ describe('LanguageClientWrapper', () => { ); }; const createWrapperWithMonitor = (options: Parameters<typeof createWrapper>[0] = {}) => { const wrapperWithMonitor = createWrapper(options); wrapperWithMonitor.startupMonitor = new LanguageServerStartupMonitor( options.mockTelemetryEnvironment ?? gitLabTelemetryEnvironment, new NoopObservabilityService(), ); return wrapperWithMonitor; }; beforeEach(() => { const gitLabPlatform: GitLabPlatformForAccount = gitlabPlatformForAccount; getGitLabPlatformMock.mockResolvedValue(gitLabPlatform); Loading Loading @@ -174,7 +185,7 @@ describe('LanguageClientWrapper', () => { describe('startup monitor', () => { it('resolves when the language server reaches Running state', async () => { // default mock fires State.Running immediately - just verify it resolves wrapper = createWrapper(); wrapper = createWrapperWithMonitor(); await expect(wrapper.initAndStart()).resolves.toBeUndefined(); }); Loading @@ -185,7 +196,7 @@ describe('LanguageClientWrapper', () => { ...client, onDidChangeState: jest.fn(() => ({ dispose: jest.fn() })), // never fires }); wrapper = createWrapper(); wrapper = createWrapperWithMonitor(); const promise = wrapper.initAndStart(); jest.advanceTimersByTime(10000); Loading @@ -204,7 +215,7 @@ describe('LanguageClientWrapper', () => { return { dispose: jest.fn() }; }), }); wrapper = createWrapper(); wrapper = createWrapperWithMonitor(); const promise = wrapper.initAndStart(); jest.advanceTimersByTime(30000); Loading
src/common/language_server/language_client_wrapper.ts +3 −5 Original line number Diff line number Diff line Loading @@ -76,7 +76,7 @@ const createRequestFn = client.sendRequest(method, param); export interface LanguageClientWrapper { startupMonitor: LanguageServerStartupMonitor; startupMonitor: LanguageServerStartupMonitor | undefined; setCustomPlatformConfig(clientConfig: Partial<ClientConfig>): void; initAndStart(): Promise<void>; syncConfig(): Promise<void>; Loading Loading @@ -128,11 +128,10 @@ export class LanguageClientWrapperImpl implements LanguageClientWrapper { #platformClientConfig?: Partial<ClientConfig>; #startupMonitor: LanguageServerStartupMonitor; #startupMonitor: LanguageServerStartupMonitor | undefined; set startupMonitor(monitor: LanguageServerStartupMonitor) { this.#startupMonitor = monitor; this.#subscriptions.push(monitor); } constructor( Loading @@ -157,7 +156,6 @@ export class LanguageClientWrapperImpl implements LanguageClientWrapper { this.#documentQualityHandler = new DocumentQualityHandler(); this.#repositoryClient = repositoryClient; this.#subscriptions.push(this.#documentQualityHandler); this.#startupMonitor = new LanguageServerStartupMonitor(telemetryEnvironment); } setCustomPlatformConfig(clientConfig: Partial<ClientConfig>): void { Loading Loading @@ -257,7 +255,7 @@ export class LanguageClientWrapperImpl implements LanguageClientWrapper { this.#documentQualityHandler.getDiagnostics, ); const startupComplete = this.#startupMonitor.observe(this.#client); const startupComplete = this.#startupMonitor?.observe(this.#client); this.#client.start().catch(() => { // Rejection here is expected during crash/restart cycles - the monitor tracks the outcome }); Loading
src/common/language_server/language_server_manager.test.ts +15 −8 Original line number Diff line number Diff line Loading @@ -30,11 +30,13 @@ import { LanguageClientWrapperImpl } from './language_client_wrapper'; import { LanguageServerManager } from './language_server_manager'; import { LanguageClientFactory } from './client_factory'; import { LanguageServerFeatureStateProvider } from './language_server_feature_state_provider'; import { LanguageServerStartupMonitor } from './language_server_startup_monitor'; jest.mock('../code_suggestions/code_suggestions_gutter_icon'); jest.mock('../code_suggestions/code_suggestions_status_bar_item'); jest.mock('../code_suggestions/code_suggestions_state_manager'); jest.mock('./language_client_wrapper'); jest.mock('./language_server_startup_monitor'); jest.mock('../code_suggestions/gitlab_platform_manager_for_code_suggestions'); jest.mock('../webview/setup_webviews'); jest.mock('../diagnostics/log_collector_output_channel'); Loading Loading @@ -64,9 +66,6 @@ describe('LanguageServerManager', () => { sendQuickChatMessageEvent: jest.fn(), syncConfig: jest.fn(), dispose: jest.fn(), startupMonitor: createFakePartial({ notifyHandshakeStarted: jest.fn(), }), }); clientContext = { ide: { Loading Loading @@ -95,6 +94,15 @@ describe('LanguageServerManager', () => { }); jest.mocked(LanguageClientWrapperImpl).mockReturnValue(clientWrapper); jest.mocked(GitLabPlatformManagerForCodeSuggestionsImpl).mockReturnValue(platformManager); jest.mocked(LanguageServerStartupMonitor).mockImplementation(() => { const monitor = createFakePartial<LanguageServerStartupMonitor>({ onDidChangePhase: jest.fn().mockReturnValue({ dispose: jest.fn() }), observe: jest.fn().mockResolvedValue(undefined), notifyHandshakeStarted: jest.fn(), dispose: jest.fn(), }); return monitor as unknown as LanguageServerStartupMonitor; }); client = createFakePartial<BaseLanguageClient>({ stop: jest.fn(), Loading Loading @@ -233,14 +241,13 @@ describe('LanguageServerManager', () => { it('calls notifyHandshakeStarted on the startupMonitor when initializationOptions is invoked', () => { const { initializationOptions } = jest.mocked(languageClientFactory.createLanguageClient).mock .calls[0][1]; const notifyHandshakeStarted = jest.spyOn( clientWrapper.startupMonitor, 'notifyHandshakeStarted', ); const mockResults = jest.mocked(LanguageServerStartupMonitor).mock.results; const monitorInstance = mockResults[mockResults.length - 1] .value as LanguageServerStartupMonitor; (initializationOptions as () => unknown)(); expect(notifyHandshakeStarted).toHaveBeenCalled(); expect(monitorInstance.notifyHandshakeStarted).toHaveBeenCalled(); }); it('initializes state manager', async () => { Loading
src/common/language_server/language_server_manager.ts +7 −2 Original line number Diff line number Diff line Loading @@ -103,7 +103,8 @@ export class LanguageServerManager implements WebviewManager, VersionProvider { return; } const { gitLabPlatformManager, gitLabTelemetryEnvironment } = this.#dependencyContainer; const { gitLabPlatformManager, gitLabTelemetryEnvironment, observabilityService } = this.#dependencyContainer; const stateManager = new CodeSuggestionsStateManager( gitLabPlatformManager, this.#context, Loading @@ -112,7 +113,10 @@ export class LanguageServerManager implements WebviewManager, VersionProvider { const statusBarItem = new CodeSuggestionsStatusBarItem(stateManager); const gutterIcon = new CodeSuggestionsGutterIcon(this.#context, stateManager); const middleware = new LanguageClientMiddleware(stateManager); const startupMonitor = new LanguageServerStartupMonitor(gitLabTelemetryEnvironment); const startupMonitor = new LanguageServerStartupMonitor( gitLabTelemetryEnvironment, observabilityService, ); const startupStatusBarItem = new LanguageServerStartupStatusBarItem(startupMonitor); const baseAssetsUrl = vscode.Uri.joinPath( this.#context.extensionUri, Loading Loading @@ -162,6 +166,7 @@ export class LanguageServerManager implements WebviewManager, VersionProvider { await this.#wrapper.initAndStart(); const subscriptions = [ this.#wrapper, startupMonitor, startupStatusBarItem, outputChannel, vscode.commands.registerCommand( Loading
src/common/language_server/language_server_startup_monitor.test.ts +5 −1 Original line number Diff line number Diff line Loading @@ -3,6 +3,7 @@ import { createFakePartial } from '../test_utils/create_fake_partial'; import { log } from '../log'; import { Snowplow } from '../snowplow/snowplow'; import { GitLabTelemetryEnvironment } from '../platform/gitlab_telemetry_environment'; import { NoopObservabilityService } from '../observability'; import { LanguageServerStartupMonitor } from './language_server_startup_monitor'; jest.mock('../log'); Loading @@ -26,7 +27,10 @@ describe('LanguageServerStartupMonitor', () => { const telemetryEnvironment = createFakePartial<GitLabTelemetryEnvironment>({ getOs: jest.fn().mockReturnValue('linux'), }); monitor = new LanguageServerStartupMonitor(telemetryEnvironment); monitor = new LanguageServerStartupMonitor( telemetryEnvironment, new NoopObservabilityService(), ); client = createFakePartial<BaseLanguageClient>({ onDidChangeState: jest.fn(handler => { onDidChangeStateHandler = handler; Loading