diff --git a/extension/src/tasks/taskUtil.ts b/extension/src/tasks/taskUtil.ts index 900d6c5f1..7d040411a 100644 --- a/extension/src/tasks/taskUtil.ts +++ b/extension/src/tasks/taskUtil.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { parseArgsStringToArgv } from 'string-argv'; -import { GradleTask, GradleProject, GradleBuild } from '../proto/gradle_pb'; +import { GradleProject, GradleBuild, GradleTask } from '../proto/gradle_pb'; import { TaskArgs } from '../stores/types'; import { GradleTaskDefinition } from '.'; import { GradleRunnerTerminal } from '../terminal'; @@ -246,35 +246,24 @@ function getVSCodeTasksFromGradleProject( gradleProject: GradleProject, client: GradleClient ): vscode.Task[] { - const gradleTasks: GradleTask[] | void = gradleProject.getTasksList(); - const vsCodeTasks = []; - try { - vsCodeTasks.push( - ...gradleTasks.map((gradleTask) => + let projects: Array = [gradleProject]; + const vsCodeTasks: vscode.Task[] = []; + while (projects.length) { + const project = projects.pop(); + const gradleTasks: GradleTask[] | void = project!.getTasksList(); + for (const gradleTask of gradleTasks) { + vsCodeTasks.push( createVSCodeTaskFromGradleTask( taskTerminalsStore, gradleTask, rootProject, client ) - ) - ); - } catch (err) { - logger.error( - 'Unable to generate vscode tasks from gradle tasks:', - err.message - ); + ); + } + projects = projects.concat(project!.getProjectsList()); } - gradleProject.getProjectsList().forEach((project) => { - vsCodeTasks.push( - ...getVSCodeTasksFromGradleProject( - taskTerminalsStore, - rootProject, - project, - client - ) - ); - }); + return vsCodeTasks; } @@ -290,20 +279,19 @@ export async function loadTasksForProjectRoots( client: GradleClient, rootProjects: ReadonlyArray ): Promise { - const allTasks: vscode.Task[] = []; + let allTasks: vscode.Task[] = []; for (const rootProject of rootProjects) { if (getConfigIsAutoDetectionEnabled(rootProject)) { const gradleBuild = await getGradleBuild(client, rootProject); const gradleProject = gradleBuild && gradleBuild.getProject(); if (gradleProject) { - allTasks.push( - ...getVSCodeTasksFromGradleProject( - taskTerminalsStore, - rootProject, - gradleProject, - client - ) + const vsCodeTasks = getVSCodeTasksFromGradleProject( + taskTerminalsStore, + rootProject, + gradleProject, + client ); + allTasks = allTasks.concat(vsCodeTasks); } } } diff --git a/extension/src/terminal/GradleRunnerTerminal.ts b/extension/src/terminal/GradleRunnerTerminal.ts index c1b9fc353..c2a5eaff9 100644 --- a/extension/src/terminal/GradleRunnerTerminal.ts +++ b/extension/src/terminal/GradleRunnerTerminal.ts @@ -17,7 +17,7 @@ const nlRegExp = new RegExp(`${NL}([^${CR}]|$)`, 'g'); export class GradleRunnerTerminal { private readonly writeEmitter = new vscode.EventEmitter(); - private stdOutLoggerStream: LoggerStream; + private stdOutLoggerStream: LoggerStream | undefined; private readonly closeEmitter = new vscode.EventEmitter(); private task?: vscode.Task; public readonly onDidWrite: vscode.Event = this.writeEmitter.event; @@ -29,8 +29,10 @@ export class GradleRunnerTerminal { private readonly cancellationKey: string, private readonly client: GradleClient ) { - // TODO: this is only needed for the tests. Find a better way to test task output in the tests. - this.stdOutLoggerStream = new LoggerStream(logger, LogVerbosity.INFO); + if (isTest()) { + // TODO: this is only needed for the tests. Find a better way to test task output in the tests. + this.stdOutLoggerStream = new LoggerStream(logger, LogVerbosity.INFO); + } } public async open(): Promise { @@ -121,7 +123,7 @@ export class GradleRunnerTerminal { private handleOutput = (output: Output): void => { const messageBytes = output.getOutputBytes_asU8(); if (messageBytes.length) { - if (isTest()) { + if (isTest() && this.stdOutLoggerStream) { this.stdOutLoggerStream.write(messageBytes); } this.write(new util.TextDecoder('utf-8').decode(messageBytes)); diff --git a/extension/src/test/unit/taskUtil.test.ts b/extension/src/test/unit/taskUtil.test.ts new file mode 100644 index 000000000..cd8a54987 --- /dev/null +++ b/extension/src/test/unit/taskUtil.test.ts @@ -0,0 +1,134 @@ +/* eslint-disable sonarjs/no-duplicate-string */ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import * as assert from 'assert'; +import * as sinon from 'sinon'; +import * as vscode from 'vscode'; +import { GradleBuild, GradleProject, GradleTask } from '../../proto/gradle_pb'; +import { RootProject } from '../../rootProject/RootProject'; +import { TaskTerminalsStore } from '../../stores'; +import { loadTasksForProjectRoots } from '../../tasks/taskUtil'; +import { buildMockClient, getSuiteName } from '../testUtil'; + +const mockWorkspaceFolder: vscode.WorkspaceFolder = { + index: 0, + uri: vscode.Uri.file('folder1'), + name: 'folder1', +}; + +const mockRootProject = new RootProject( + mockWorkspaceFolder, + vscode.Uri.file('folder1'), + { + tasks: [], + clean: false, + } +); +const mockClient = buildMockClient(); +const mockTaskTerminalsStore = new TaskTerminalsStore(); + +function buildProject( + project: string, + rootProject: string, + isRoot: boolean, + tasksPerProject: number +): GradleProject { + const gradleProject = new GradleProject(); + gradleProject.setIsRoot(isRoot); + + const tasks: Array = []; + for (let i = 0; i < tasksPerProject; i++) { + const gradleTask = new GradleTask(); + gradleTask.setName(`test-${project}-task-name-${Math.random()}`); + gradleTask.setProject(project); + gradleTask.setRootproject(rootProject); + tasks.push(gradleTask); + } + + gradleProject.setTasksList(tasks); + return gradleProject; +} + +function getDeeplyNestedProjectTree( + amountOfProjects: number, + tasksPerProject: number, + levels: number, + currentLevel = 1 +): Array { + const projects: Array = []; + for (let i = 0; i < amountOfProjects; i++) { + const project = buildProject( + 'child-project', + 'root-project', + false, + tasksPerProject + ); + if (currentLevel < levels) { + project.setProjectsList( + getDeeplyNestedProjectTree( + amountOfProjects, + tasksPerProject, + levels, + currentLevel + 1 + ) + ); + } + projects.push(project); + } + return projects; +} + +describe(getSuiteName('taskUtil'), () => { + afterEach(() => { + sinon.restore(); + }); + + it('should create vscode tasks', async () => { + const gradleBuild = new GradleBuild(); + const rootGradleProject = buildProject( + 'root-project', + 'root-project', + true, + 1 + ); + const childGradleProject = buildProject( + 'child-project', + 'root-project', + false, + 1 + ); + rootGradleProject.setProjectsList([childGradleProject]); + gradleBuild.setProject(rootGradleProject); + mockClient.getBuild.resolves(Promise.resolve(gradleBuild)); + + const tasks = await loadTasksForProjectRoots( + mockTaskTerminalsStore, + mockClient, + [mockRootProject] + ); + assert.strictEqual(tasks.length, 2); + }); + + it('should create vscode tasks for a super-massive project without throwing a callstack error', async () => { + const gradleBuild = new GradleBuild(); + const rootGradleProject = buildProject( + 'root-project', + 'root-project', + true, + 1 + ); + + const projectsList = getDeeplyNestedProjectTree(50, 2, 3); + + rootGradleProject.setProjectsList(projectsList); + gradleBuild.setProject(rootGradleProject); + mockClient.getBuild.resolves(Promise.resolve(gradleBuild)); + + const tasks = await loadTasksForProjectRoots( + mockTaskTerminalsStore, + mockClient, + [mockRootProject] + ); + assert.strictEqual(tasks.length, 255101); + }); +});