Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add load project logic at client side #1239

Merged
merged 1 commit into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions src/controller/testController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as _ from 'lodash';
import { CancellationToken, TestController, TestItem, tests } from 'vscode';
import { isStandardServerReady } from '../extension';
import { loadJavaProjects } from './utils';

export let testController: TestController | undefined;

export function createTestController(): void {
if (!isStandardServerReady()) {
return;
}
testController?.dispose();
testController = tests.createTestController('javaTestController', 'Java Test');

testController.resolveHandler = async (item: TestItem) => {
await loadChildren(item);
};
}

export async function loadChildren(item: TestItem, token?: CancellationToken): Promise<void> {
if (!item) {
await loadJavaProjects();
return;
}
// todo: load other items
}
36 changes: 36 additions & 0 deletions src/controller/testItemDataCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import { TestItem } from 'vscode';
import { TestKind, TestLevel } from '../types';

/**
* A map cache to save the metadata of the test item.
* Use a WeakMap here so the key-value will be automatically collected when
* the actual item is disposed.
*/
class TestItemDataCache {
private cache: WeakMap<TestItem, ITestItemData> = new WeakMap();

public set(item: TestItem, data: ITestItemData): void {
this.cache.set(item, data);
}

public get(item: TestItem): ITestItemData | undefined {
return this.cache.get(item);
}

public delete(item: TestItem): boolean {
return this.cache.delete(item);
}
}

export const dataCache: TestItemDataCache = new TestItemDataCache();

export interface ITestItemData {
jdtHandler: string;
fullName: string;
projectName: string;
testLevel: TestLevel;
testKind: TestKind;
}
74 changes: 74 additions & 0 deletions src/controller/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import * as _ from 'lodash';
import { CancellationToken, Range, TestItem, Uri, workspace, WorkspaceFolder } from 'vscode';
import { JavaTestRunnerDelegateCommands } from '../constants';
import { IJavaTestItem, TestLevel } from '../types';
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
import { testController } from './testController';
import { dataCache } from './testItemDataCache';

/**
* Load the Java projects, which are the root nodes of the test explorer
*/
export async function loadJavaProjects(): Promise<void> {
for (const workspaceFolder of workspace.workspaceFolders || [] ) {
const testProjects: IJavaTestItem[] = await getJavaProjects(workspaceFolder);
for (const project of testProjects) {
if (testController?.items.get(project.id)) {
continue;
}
const projectItem: TestItem = createTestItem(project);
projectItem.canResolveChildren = true;
testController?.items.add(projectItem);
}
}
}

/**
* Create test item which will be shown in the test explorer
* @param metaInfo The data from the server side of the test item
* @param parent The parent node of the test item (if it has)
* @returns The created test item
*/
export function createTestItem(metaInfo: IJavaTestItem, parent?: TestItem): TestItem {
if (!testController) {
throw new Error('Failed to create test item. The test controller is not initialized.');
}
const item: TestItem = testController.createTestItem(
metaInfo.id,
metaInfo.label,
metaInfo.uri ? Uri.parse(metaInfo.uri) : undefined,
);
item.range = asRange(metaInfo.range);
if (metaInfo.testLevel !== TestLevel.Invocation) {
dataCache.set(item, {
jdtHandler: metaInfo.jdtHandler,
fullName: metaInfo.fullName,
projectName: metaInfo.projectName,
testLevel: metaInfo.testLevel,
testKind: metaInfo.testKind,
});
}
if (parent) {
parent.children.add(item);
}
return item;
}

/**
* Parse the range object with server mode to client format
* @param range
*/
export function asRange(range: any): Range | undefined {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it lsp2code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

if (!range) {
return undefined;
}
return new Range(range.start.line, range.start.character, range.end.line, range.end.character);
}

export async function getJavaProjects(workspaceFolder: WorkspaceFolder, token?: CancellationToken): Promise<IJavaTestItem[]> {
return await executeJavaLanguageServerCommand<IJavaTestItem[]>(
JavaTestRunnerDelegateCommands.FIND_JAVA_PROJECTS, workspaceFolder.uri.toString(), token) || [];
}
8 changes: 8 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { commands, Event, Extension, ExtensionContext, extensions, Uri } from 'v
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation } from 'vscode-extension-telemetry-wrapper';
import { registerAdvanceAskForChoice, registerAskForChoiceCommand, registerAskForInputCommand } from './commands/generationCommands';
import { Context, ExtensionName, VSCodeCommands } from './constants';
import { createTestController, testController } from './controller/testController';
import { IProgressProvider } from './debugger.api';
import { initExpService } from './experimentationService';
import { disposeCodeActionProvider, registerTestCodeActionProvider } from './provider/codeActionProvider';
Expand All @@ -22,6 +23,7 @@ export async function activate(context: ExtensionContext): Promise<void> {
export async function deactivate(): Promise<void> {
disposeCodeActionProvider();
await disposeTelemetryWrapper();
testController?.dispose();
}

async function doActivate(_operationId: string, context: ExtensionContext): Promise<void> {
Expand Down Expand Up @@ -51,6 +53,7 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom
// Only re-initialize the component when its lightweight -> standard
if (mode === LanguageServerMode.Standard) {
registerTestCodeActionProvider();
createTestController();
}
}));
}
Expand All @@ -71,6 +74,11 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom
registerAskForChoiceCommand(context);
registerAdvanceAskForChoice(context);
registerAskForInputCommand(context);

if (isStandardServerReady()) {
registerTestCodeActionProvider();
createTestController();
}
}

export function isStandardServerReady(): boolean {
Expand Down
38 changes: 38 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import { Range } from 'vscode';

/**
* The test item data model that is returned from server side
*/
export interface IJavaTestItem {
children: IJavaTestItem[];
uri: string | undefined;
range: Range | undefined;
jdtHandler: string;
fullName: string;
label: string;
id: string;
projectName: string;
testKind: TestKind;
testLevel: TestLevel;
}

export enum TestKind {
JUnit5 = 0,
JUnit = 1,
TestNG = 2,
None = 100,
}

export enum TestLevel {
Root = 0,
Workspace = 1,
WorkspaceFolder = 2,
Project = 3,
Package = 4,
Class = 5,
Method = 6,
Invocation = 7,
}
75 changes: 6 additions & 69 deletions src/utils/commandUtils.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

import { CancellationToken, commands, Position, Uri } from 'vscode';
import { ITestSourcePath } from '../../extension.bundle';
import { JavaLanguageServerCommands, JavaTestRunnerDelegateCommands } from '../constants/commands';
import { logger } from '../logger/logger';
import { ILocation, ISearchTestItemParams, ITestItem, TestKind, TestLevel } from '../protocols';
import { IJUnitLaunchArguments } from '../runners/baseRunner/BaseRunner';
import { commands } from 'vscode';
import { sendError } from 'vscode-extension-telemetry-wrapper';
import { JavaLanguageServerCommands } from '../constants';

export async function getTestSourcePaths(uri: string[]): Promise<ITestSourcePath[]> {
return await executeJavaLanguageServerCommand<ITestSourcePath[]>(
JavaTestRunnerDelegateCommands.GET_TEST_SOURCE_PATH, uri) || [];
}

export async function searchTestItems(params: ISearchTestItemParams): Promise<ITestItem[]> {
return await executeJavaLanguageServerCommand<ITestItem[]>(
JavaTestRunnerDelegateCommands.SEARCH_TEST_ITEMS, JSON.stringify(params)) || [];
}

export async function searchTestItemsAll(request: ISearchTestItemParams, token: CancellationToken): Promise<ITestItem[]> {
return await executeJavaLanguageServerCommand<ITestItem[]>(
JavaTestRunnerDelegateCommands.SEARCH_TEST_ITEMS_ALL, JSON.stringify(request), token) || [];
}

export async function searchTestCodeLens(uri: string, token?: CancellationToken): Promise<ITestItem[]> {
if (token) {
return await executeJavaLanguageServerCommand<ITestItem[]>(
JavaTestRunnerDelegateCommands.SEARCH_TEST_CODE_LENS, uri, token) || [];
}
return await executeJavaLanguageServerCommand<ITestItem[]>(
JavaTestRunnerDelegateCommands.SEARCH_TEST_CODE_LENS, uri) || [];
}

export async function searchTestLocation(fullName: string): Promise<ILocation[]> {
return await executeJavaLanguageServerCommand<ILocation[]>(
JavaTestRunnerDelegateCommands.SEARCH_TEST_LOCATION, fullName) || [];
}

export async function resolveStackTraceLocation(trace: string, projectNames: string[]): Promise<string> {
return await executeJavaLanguageServerCommand<string>(
JavaLanguageServerCommands.RESOLVE_STACKTRACE_LOCATION, trace, projectNames) || '';
}

export async function generateTests(uri: Uri, cursorOffset: number): Promise<any> {
return await executeJavaLanguageServerCommand<any>(JavaTestRunnerDelegateCommands.GENERATE_TESTS, uri.toString(), cursorOffset);
}

export async function resolveJUnitLaunchArguments(uri: string, fullName: string, testName: string, project: string,
scope: TestLevel, testKind: TestKind, start?: Position, end?: Position,
isHierarchicalPackage?: boolean): Promise<IJUnitLaunchArguments> {
const argument: IJUnitLaunchArguments | undefined = await executeJavaLanguageServerCommand<IJUnitLaunchArguments>(
JavaTestRunnerDelegateCommands.RESOLVE_JUNIT_ARGUMENT, JSON.stringify({
uri,
fullName,
testName,
project,
scope,
testKind,
start,
end,
isHierarchicalPackage,
}));

if (!argument) {
throw new Error('Failed to parse the JUnit launch arguments');
}

return argument;
}

async function executeJavaLanguageServerCommand<T>(...rest: any[]): Promise<T | undefined> {
export async function executeJavaLanguageServerCommand<T>(...rest: any[]): Promise<T | undefined> {
try {
return await commands.executeCommand<T>(JavaLanguageServerCommands.EXECUTE_WORKSPACE_COMMAND, ...rest);
} catch (error) {
if (isCancelledError(error)) {
return;
}
logger.error(error.toString());
const parsedError: Error = new Error(`Failed to execute: ${rest[0]}, ${error.toString()}.`);
sendError(parsedError);
throw error;
}
}
Expand Down