Skip to content

Commit

Permalink
Mingxuanzhang/248 synchronize init tasks (#5163)
Browse files Browse the repository at this point in the history
* feat: add an option for waiting init jobs

* fix: suspend setting

* feat: add unit test for the new vscode setting

* fix: release 246 apex language server (#5152)

@W-13804822@

Update apex language server jar for 246

There should not be any functional changes since kast version if v58.*

* fix: disable dependabot major version prs (#5144)

* fix: upgrade language client to version 9 (#5127)

* fix: upgrade apex to use latest language modules

@W-14169322@

* chore: remove unneeded dependencies

* chore: apply review suggestions

* feat: add request for checking indexer status

* feat: refactor constants & tests

* fix: ts lint

* fix: delete unnecessary indexer status check

* fix: remove unnecessary fields

* fix: logic after client is running and refactor where configuration is defined

* feat: refactor indexerDoneHandler & implement unit test

* fix: promise error

* fix: revert apex-jorje-lsp jar

* feat: refactor index file of apex language client

* chore: some unit testing cleanup

* chore: more lint

* chore: sentence and test cleanup

* chore: sentence update

* chore: test cleanup

* chore: remove setting from package.json

* chore: unit test for settings.ts and extensionUtils.ts

---------

Co-authored-by: peternhale <[email protected]>
Co-authored-by: Cristina Cañizales <[email protected]>
Co-authored-by: gbockus-sf <[email protected]>
Co-authored-by: Gordon Bockus <[email protected]>
  • Loading branch information
5 people authored Nov 3, 2023
1 parent 177f3da commit 28b3e38
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 27 deletions.
3 changes: 3 additions & 0 deletions packages/salesforcedx-vscode-apex/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const FAIL_RESULT = 'Fail';
export const SKIP_RESULT = 'Skip';

export const APEX_TESTS = 'ApexTests';
export const API = {
doneIndexing: 'indexer/done'
};
export const UBER_JAR_NAME = 'apex-jorje-lsp.jar';
export const APEX_LSP_STARTUP = 'apexLSPStartup';
export const APEX_LSP_ORPHAN = 'apexLSPOrphan';
Expand Down
57 changes: 42 additions & 15 deletions packages/salesforcedx-vscode-apex/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import * as vscode from 'vscode';
import { ApexLanguageClient } from './apexLanguageClient';
import ApexLSPStatusBarItem from './apexLspStatusBarItem';
import { CodeCoverage, StatusBarToggle } from './codecoverage';
import { API } from './constants';
import { retrieveEnableSyncInitJobs } from './settings';

import {
forceAnonApexDebug,
forceAnonApexExecute,
Expand All @@ -30,10 +33,11 @@ import {
import { SET_JAVA_DOC_LINK } from './constants';
import { workspaceContext } from './context';
import * as languageServer from './languageServer';
import {languageServerOrphanHandler as lsoh} from './languageServerOrphanHandler';
import { languageServerOrphanHandler as lsoh } from './languageServerOrphanHandler';
import {
ClientStatus,
enableJavaDocSymbols,
extensionUtils,
getApexTests,
getExceptionBreakpointInfo,
getLineBreakpointInfo,
Expand All @@ -45,10 +49,10 @@ import { getTestOutlineProvider } from './views/testOutlineProvider';
import { ApexTestRunner, TestRunType } from './views/testRunner';

let languageClient: ApexLanguageClient | undefined;
const languageServerStatusBarItem = new ApexLSPStatusBarItem();

export async function activate(extensionContext: vscode.ExtensionContext) {
const extensionHRStart = process.hrtime();
const languageServerStatusBarItem = new ApexLSPStatusBarItem();
const testOutlineProvider = getTestOutlineProvider();
if (vscode.workspace && vscode.workspace.workspaceFolders) {
const apexDirPath = getTestResultsFolder(
Expand Down Expand Up @@ -79,7 +83,7 @@ export async function activate(extensionContext: vscode.ExtensionContext) {
await telemetryService.initializeService(extensionContext);

// start the language server and client
await createLanguageClient(extensionContext);
await createLanguageClient(extensionContext, languageServerStatusBarItem);

// Javadoc support
enableJavaDocSymbols();
Expand Down Expand Up @@ -283,7 +287,10 @@ export async function deactivate() {
telemetryService.sendExtensionDeactivationEvent();
}

async function createLanguageClient(extensionContext: vscode.ExtensionContext) {
async function createLanguageClient(
extensionContext: vscode.ExtensionContext,
languageServerStatusBarItem: ApexLSPStatusBarItem
) {
// Initialize Apex language server
try {
const langClientHRStart = process.hrtime();
Expand All @@ -292,10 +299,6 @@ async function createLanguageClient(extensionContext: vscode.ExtensionContext) {
);

if (languageClient) {
languageClient.onNotification('indexer/done', async () => {
await getTestOutlineProvider().refresh();
languageServerReady();
});
languageClient.errorHandler?.addListener('error', message => {
languageServerStatusBarItem.error(message);
});
Expand All @@ -318,12 +321,16 @@ async function createLanguageClient(extensionContext: vscode.ExtensionContext) {
void lsoh.resolveAnyFoundOrphanLanguageServers();

await languageClient!.start();

const startTime = telemetryService.getEndHRTime(langClientHRStart);
// Client is running
const startTime = telemetryService.getEndHRTime(langClientHRStart); // Record the end time
telemetryService.sendEventData('apexLSPStartup', undefined, {
activationTime: startTime
});
languageClientUtils.setStatus(ClientStatus.Indexing, '');
await indexerDoneHandler(
retrieveEnableSyncInitJobs(),
languageClient,
languageServerStatusBarItem
);
extensionContext.subscriptions.push(languageClient);
} catch (e) {
languageClientUtils.setStatus(ClientStatus.Error, e);
Expand All @@ -340,8 +347,28 @@ async function createLanguageClient(extensionContext: vscode.ExtensionContext) {
}
}

export function languageServerReady() {
languageServerStatusBarItem.ready();
languageClientUtils.setStatus(ClientStatus.Ready, '');
languageClient?.errorHandler?.serviceHasStartedSuccessfully();
// exported only for test
export async function indexerDoneHandler(
enableSyncInitJobs: boolean,
languageClient: ApexLanguageClient,
languageServerStatusBarItem: ApexLSPStatusBarItem
) {
// Listener is useful only in async mode
if (!enableSyncInitJobs) {
// The listener should be set after languageClient is ready
// Language client will get notified once async init jobs are done
languageClientUtils.setStatus(ClientStatus.Indexing, '');
languageClient.onNotification(API.doneIndexing, async () => {
await extensionUtils.setClientReady(
languageClient,
languageServerStatusBarItem
);
});
} else {
// indexer must be running at the point
await extensionUtils.setClientReady(
languageClient,
languageServerStatusBarItem
);
}
}
13 changes: 10 additions & 3 deletions packages/salesforcedx-vscode-apex/src/languageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@

import * as path from 'path';
import * as vscode from 'vscode';
import { Executable, LanguageClientOptions, RevealOutputChannelOn } from 'vscode-languageclient/node';
import {
Executable,
LanguageClientOptions,
RevealOutputChannelOn
} from 'vscode-languageclient/node';
import { ApexErrorHandler } from './apexErrorHandler';
import { ApexLanguageClient } from './apexLanguageClient';
import { LSP_ERR, UBER_JAR_NAME } from './constants';
import { soqlMiddleware } from './embeddedSoql';
import { nls } from './messages';
import * as requirements from './requirements';
import { retrieveEnableSyncInitJobs } from './settings';
import { telemetryService } from './telemetry';

const JDWP_DEBUG_PORT = 2739;
Expand Down Expand Up @@ -69,7 +74,8 @@ async function createServer(
args.push(
'-Dtrace.protocol=false',
`-Dapex.lsp.root.log.level=${LANGUAGE_SERVER_LOG_LEVEL}`,
`-agentlib:jdwp=transport=dt_socket,server=y,suspend=${SUSPEND_LANGUAGE_SERVER_STARTUP ? 'y' : 'n'
`-agentlib:jdwp=transport=dt_socket,server=y,suspend=${
SUSPEND_LANGUAGE_SERVER_STARTUP ? 'y' : 'n'
},address=*:${JDWP_DEBUG_PORT},quiet=y`
);
if (process.env.YOURKIT_PROFILER_AGENT) {
Expand Down Expand Up @@ -168,7 +174,8 @@ export function buildClientOptions(): LanguageClientOptions {
protocol2Code: protocol2CodeConverter
},
initializationOptions: {
enableEmbeddedSoqlCompletion: soqlExtensionInstalled
enableEmbeddedSoqlCompletion: soqlExtensionInstalled,
enableSynchronizedInitJobs: retrieveEnableSyncInitJobs()
},
...(soqlExtensionInstalled ? { middleware: soqlMiddleware } : {}),
errorHandler: new ApexErrorHandler()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApexLanguageClient } from '../apexLanguageClient';
import ApexLSPStatusBarItem from '../apexLspStatusBarItem';
import { getTestOutlineProvider } from '../views/testOutlineProvider';
import {
ClientStatus,
languageClientUtils
} from './index';

const setClientReady = async (languageClient: ApexLanguageClient, languageServerStatusBarItem: ApexLSPStatusBarItem) => {
await getTestOutlineProvider().refresh();
languageServerStatusBarItem.ready();
languageClientUtils.setStatus(ClientStatus.Ready, '');
languageClient?.errorHandler?.serviceHasStartedSuccessfully();
};

export const extensionUtils = {
setClientReady
};
2 changes: 2 additions & 0 deletions packages/salesforcedx-vscode-apex/src/languageUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ export {
export { enableJavaDocSymbols } from './javaDocSymbols';

export { languageServerUtils, ProcessDetail } from './languageServerUtils';

export { extensionUtils } from './extensionUtils';
6 changes: 6 additions & 0 deletions packages/salesforcedx-vscode-apex/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ export function retrieveTestCodeCoverage(): boolean {
.getConfiguration(SFDX_CORE_CONFIGURATION_NAME)
.get<boolean>('retrieve-test-code-coverage', false);
}

export function retrieveEnableSyncInitJobs(): boolean {
return vscode.workspace
.getConfiguration()
.get<boolean>('salesforcedx-vscode-apex.wait-init-jobs', true);
}
64 changes: 64 additions & 0 deletions packages/salesforcedx-vscode-apex/test/jest/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { API } from '../../src/constants';
import * as index from '../../src/index';
import { languageClientUtils } from '../../src/languageUtils';
import { extensionUtils } from '../../src/languageUtils/extensionUtils';
import ApexLSPStatusBarItem from './../../src/apexLspStatusBarItem';

jest.mock('./../../src/apexLspStatusBarItem');
describe('indexDoneHandler', () => {
let setStatusSpy: jest.SpyInstance;
let onNotificationSpy: jest.SpyInstance;
let mockLanguageClient: any;
let setClientReadySpy: jest.SpyInstance;
const apexLSPStatusBarItemMock = jest.mocked(ApexLSPStatusBarItem);

beforeEach(() => {
setStatusSpy = jest
.spyOn(languageClientUtils, 'setStatus')
.mockReturnValue();
mockLanguageClient = {
onNotification: jest.fn()
};
onNotificationSpy = jest.spyOn(mockLanguageClient, 'onNotification');
setClientReadySpy = jest
.spyOn(extensionUtils, 'setClientReady')
.mockResolvedValue();
});

it('should call languageClientUtils.setStatus and set up event listener when enableSyncInitJobs is false', async () => {
const languageServerStatusBarItem = new ApexLSPStatusBarItem();
await index.indexerDoneHandler(
false,
mockLanguageClient as any,
languageServerStatusBarItem
);
expect(setStatusSpy).toHaveBeenCalledWith(1, '');
expect(onNotificationSpy).toHaveBeenCalledWith(
API.doneIndexing,
expect.any(Function)
);
expect(apexLSPStatusBarItemMock).toHaveBeenCalledTimes(1);

const mockCallback = onNotificationSpy.mock.calls[0][1];

await mockCallback();
expect(setClientReadySpy).toHaveBeenCalledWith(
mockLanguageClient,
languageServerStatusBarItem
);
});

it('should call setClientReady when enableSyncInitJobs is true', async () => {
const languageServerStatusBarItem = new ApexLSPStatusBarItem();
await index.indexerDoneHandler(
true,
mockLanguageClient as any,
languageServerStatusBarItem
);
expect(setClientReadySpy).toHaveBeenCalledWith(
mockLanguageClient,
languageServerStatusBarItem
);
expect(apexLSPStatusBarItemMock).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import ApexLSPStatusBarItem from '../../../src/apexLspStatusBarItem';
import { ClientStatus, languageClientUtils } from '../../../src/languageUtils';
import { extensionUtils } from '../../../src/languageUtils';
import * as testOutlineProvider from '../../../src/views/testOutlineProvider';

jest.mock('../../../src/apexLspStatusBarItem');
describe('extensionUtils Unit Tests.', () => {
const apexLspStatusBarItemMock = jest.mocked(ApexLSPStatusBarItem);
let refreshSpy: jest.SpyInstance;
let readySpy: jest.SpyInstance;
let setStatusSpy: jest.SpyInstance;
let serviceHasStartedSuccessfullySpy: jest.SpyInstance;
let languageServerStatusBarItem: ApexLSPStatusBarItem;
let getTestOutlineProviderSpy: jest.SpyInstance;
let mockTestOutlineProviderInst;
let mockLanguageClient: any;

beforeEach(() => {
mockTestOutlineProviderInst = {
refresh: jest.fn(() => Promise.resolve())
};
getTestOutlineProviderSpy = jest
.spyOn(testOutlineProvider, 'getTestOutlineProvider')
.mockReturnValue(mockTestOutlineProviderInst as any);

languageServerStatusBarItem = new ApexLSPStatusBarItem();
refreshSpy = jest
.spyOn(mockTestOutlineProviderInst, 'refresh')
.mockResolvedValue();
readySpy = jest.spyOn(languageServerStatusBarItem, 'ready');
setStatusSpy = jest
.spyOn(languageClientUtils, 'setStatus')
.mockReturnValue();
mockLanguageClient = {
errorHandler: {
serviceHasStartedSuccessfully: jest.fn()
}
};
serviceHasStartedSuccessfullySpy = jest.spyOn(
mockLanguageClient.errorHandler,
'serviceHasStartedSuccessfully'
);
});

it('should be executed as expected', async () => {
await extensionUtils.setClientReady(
mockLanguageClient,
languageServerStatusBarItem
);
expect(getTestOutlineProviderSpy).toHaveBeenCalled();
expect(refreshSpy).toHaveBeenCalled();
expect(readySpy).toHaveBeenCalled();
expect(setStatusSpy).toHaveBeenCalledWith(ClientStatus.Ready, '');
expect(serviceHasStartedSuccessfullySpy).toHaveBeenCalled();
expect(apexLspStatusBarItemMock).toHaveBeenCalledTimes(1);
});
});
48 changes: 48 additions & 0 deletions packages/salesforcedx-vscode-apex/test/jest/settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { SFDX_CORE_CONFIGURATION_NAME } from '@salesforce/salesforcedx-utils-vscode';
import * as vscode from 'vscode';
import {
retrieveEnableSyncInitJobs,
retrieveTestCodeCoverage
} from '../../src/settings';

describe('settings Unit Tests.', () => {
const vscodeMocked = jest.mocked(vscode);
let getConfigurationMock: jest.SpyInstance;
let getFn: jest.Mock;

beforeEach(() => {
getConfigurationMock = jest.spyOn(
vscodeMocked.workspace,
'getConfiguration'
);
getFn = jest.fn();
});

it('Should be able to get retrieveTestCodeCoverage setting.', () => {
getConfigurationMock.mockReturnValue({
get: getFn.mockReturnValue(false)
} as any);

const result = retrieveTestCodeCoverage();

expect(result).toBe(false);
expect(getConfigurationMock).toHaveBeenCalledWith(
SFDX_CORE_CONFIGURATION_NAME
);
expect(getFn).toHaveBeenCalledWith('retrieve-test-code-coverage', false);
});

it('Should be able to get retrieveEnableSyncInitJobs setting.', () => {
getConfigurationMock.mockReturnValue({
get: getFn.mockReturnValue(true)
} as any);

const result = retrieveEnableSyncInitJobs();
expect(result).toBe(true);
expect(getConfigurationMock).toHaveBeenCalledWith();
expect(getFn).toHaveBeenCalledWith(
'salesforcedx-vscode-apex.wait-init-jobs',
true
);
});
});
Loading

0 comments on commit 28b3e38

Please sign in to comment.