| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| 'use strict'; |
| |
| import { commands, debug, tests, workspace, CancellationToken, TestController, TestItem, TestRunProfileKind, TestRunRequest, Uri, TestRun, TestMessage, Location, Position } from "vscode"; |
| import * as path from 'path'; |
| import { asRange, TestCase, TestSuite } from "./protocol"; |
| |
| export class NbTestAdapter { |
| |
| private readonly testController: TestController; |
| private disposables: { dispose(): void }[] = []; |
| private currentRun: TestRun | undefined; |
| private itemsToRun: Set<TestItem> | undefined; |
| private started: boolean = false; |
| |
| constructor() { |
| this.testController = tests.createTestController('apacheNetBeansController', 'Apache NetBeans'); |
| const runHandler = (request: TestRunRequest, cancellation: CancellationToken) => this.run(request, cancellation); |
| this.testController.createRunProfile('Run Tests', TestRunProfileKind.Run, runHandler); |
| this.testController.createRunProfile('Debug Tests', TestRunProfileKind.Debug, runHandler); |
| this.disposables.push(this.testController); |
| this.load(); |
| } |
| |
| async load(): Promise<void> { |
| for (let workspaceFolder of workspace.workspaceFolders || []) { |
| const loadedTests: any = await commands.executeCommand('java.load.workspace.tests', workspaceFolder.uri.toString()); |
| if (loadedTests) { |
| loadedTests.forEach((suite: TestSuite) => { |
| this.updateTests(suite); |
| }); |
| } |
| } |
| } |
| |
| async run(request: TestRunRequest, cancellation: CancellationToken): Promise<void> { |
| if (!this.currentRun) { |
| commands.executeCommand('workbench.debug.action.focusRepl'); |
| cancellation.onCancellationRequested(() => this.cancel()); |
| this.currentRun = this.testController.createTestRun(request); |
| this.itemsToRun = new Set(); |
| this.started = false; |
| if (request.include) { |
| const include = [...new Map(request.include.map(item => !item.uri && item.parent?.uri ? [item.parent.id, item.parent] : [item.id, item])).values()]; |
| for (let item of include) { |
| if (item.uri) { |
| this.set(item, 'enqueued'); |
| const idx = item.id.indexOf(':'); |
| if (!cancellation.isCancellationRequested) { |
| await commands.executeCommand(request.profile?.kind === TestRunProfileKind.Debug ? 'java.debug.single' : 'java.run.single', item.uri.toString(), idx < 0 ? undefined : item.id.slice(idx + 1)); |
| } |
| } |
| } |
| } else { |
| this.testController.items.forEach(item => this.set(item, 'enqueued')); |
| for (let workspaceFolder of workspace.workspaceFolders || []) { |
| if (!cancellation.isCancellationRequested) { |
| await commands.executeCommand(request.profile?.kind === TestRunProfileKind.Debug ? 'java.debug.test': 'java.run.test', workspaceFolder.uri.toString()); |
| } |
| } |
| } |
| if (this.started) { |
| this.itemsToRun.forEach(item => this.set(item, 'skipped')); |
| } |
| this.itemsToRun = undefined; |
| this.currentRun.end(); |
| this.currentRun = undefined; |
| } |
| } |
| |
| set(item: TestItem, state: 'enqueued' | 'started' | 'passed' | 'failed' | 'skipped' | 'errored', message?: TestMessage | readonly TestMessage[], noPassDown? : boolean): void { |
| if (this.currentRun) { |
| switch (state) { |
| case 'enqueued': |
| this.itemsToRun?.add(item); |
| this.currentRun.enqueued(item); |
| break; |
| case 'started': |
| case 'passed': |
| case 'skipped': |
| this.itemsToRun?.delete(item); |
| this.currentRun[state](item); |
| break; |
| case 'failed': |
| case 'errored': |
| this.itemsToRun?.delete(item); |
| this.currentRun[state](item, message || new TestMessage("")); |
| break; |
| } |
| if (!noPassDown) { |
| item.children.forEach(child => this.set(child, state, message, noPassDown)); |
| } |
| } |
| } |
| |
| cancel(): void { |
| debug.stopDebugging(); |
| } |
| |
| dispose(): void { |
| this.cancel(); |
| for (const disposable of this.disposables) { |
| disposable.dispose(); |
| } |
| this.disposables = []; |
| } |
| |
| testOutput(output: string): void { |
| if (this.currentRun && output) { |
| this.currentRun.appendOutput(output.replace(/\n/g, '\r\n')); |
| } |
| } |
| |
| testProgress(suite: TestSuite): void { |
| const currentSuite = this.testController.items.get(suite.name); |
| switch (suite.state) { |
| case 'loaded': |
| this.updateTests(suite); |
| break; |
| case 'started': |
| this.started = true; |
| if (currentSuite) { |
| this.set(currentSuite, 'started'); |
| } |
| break; |
| case 'passed': |
| case "failed": |
| case 'errored': |
| case 'skipped': |
| if (suite.tests) { |
| this.updateTests(suite, true); |
| if (currentSuite) { |
| const suiteMessages: TestMessage[] = []; |
| suite.tests?.forEach(test => { |
| if (this.currentRun) { |
| let currentTest = currentSuite.children.get(test.id); |
| if (!currentTest) { |
| currentSuite.children.forEach(item => { |
| if (!currentTest) { |
| const subName = this.subTestName(item, test); |
| if (subName) { |
| currentTest = subName === '()' ? item : item.children.get(test.id); |
| } |
| } |
| }); |
| } |
| let message: TestMessage | undefined; |
| if (test.stackTrace) { |
| message = new TestMessage(test.stackTrace.join('\n')); |
| if (currentTest) { |
| const testUri = currentTest.uri || currentTest.parent?.uri; |
| if (testUri) { |
| const fileName = path.basename(testUri.path); |
| const line = test.stackTrace.map(frame => { |
| const info = frame.match(/^\s*at[^\(]*\((\S*):(\d*)\)$/); |
| if (info && info.length >= 3 && info[1] === fileName) { |
| return parseInt(info[2]); |
| } |
| return null; |
| }).find(l => l); |
| const pos = line ? new Position(line - 1, 0) : currentTest.range?.start; |
| if (pos) { |
| message.location = new Location(testUri, pos); |
| } |
| } |
| } else { |
| message.location = new Location(currentSuite.uri!, currentSuite.range!.start); |
| } |
| } |
| if (currentTest && test.state !== 'loaded') { |
| this.set(currentTest, test.state, message, true); |
| } else if (test.state !== 'passed' && message) { |
| suiteMessages.push(message); |
| } |
| } |
| }); |
| if (suiteMessages.length > 0) { |
| this.set(currentSuite, 'errored', suiteMessages, true); |
| currentSuite.children.forEach(item => this.set(item, 'skipped')); |
| } else { |
| this.set(currentSuite, suite.state, undefined, true); |
| } |
| } |
| } |
| break; |
| } |
| } |
| |
| updateTests(suite: TestSuite, testExecution?: boolean): void { |
| let currentSuite = this.testController.items.get(suite.name); |
| const suiteUri = suite.file ? Uri.parse(suite.file) : undefined; |
| if (!currentSuite || suiteUri && currentSuite.uri?.toString() !== suiteUri.toString()) { |
| currentSuite = this.testController.createTestItem(suite.name, suite.name, suiteUri); |
| this.testController.items.add(currentSuite); |
| } |
| const suiteRange = asRange(suite.range); |
| if (!testExecution && suiteRange && suiteRange !== currentSuite.range) { |
| currentSuite.range = suiteRange; |
| } |
| const children: TestItem[] = [] |
| const parentTests: Map<TestItem, TestItem[]> = new Map(); |
| suite.tests?.forEach(test => { |
| let currentTest = currentSuite?.children.get(test.id); |
| const testUri = test.file ? Uri.parse(test.file) : undefined; |
| if (currentTest) { |
| if (currentTest.uri?.toString() !== testUri?.toString()) { |
| currentTest = this.testController.createTestItem(test.id, test.name, testUri); |
| currentSuite?.children.add(currentTest); |
| } |
| const testRange = asRange(test.range); |
| if (!testExecution && testRange && testRange !== currentTest.range) { |
| currentTest.range = testRange; |
| } |
| children.push(currentTest); |
| } else { |
| if (testExecution) { |
| const parents: Map<TestItem, string> = new Map(); |
| currentSuite?.children.forEach(item => { |
| const subName = this.subTestName(item, test); |
| if (subName && '()' !== subName) { |
| parents.set(item, subName); |
| } |
| }); |
| const parent = this.selectParent(parents); |
| if (parent) { |
| let arr = parentTests.get(parent.test); |
| if (!arr) { |
| parentTests.set(parent.test, arr = []); |
| children.push(parent.test); |
| } |
| arr.push(this.testController.createTestItem(test.id, parent.label)); |
| } |
| } else { |
| currentTest = this.testController.createTestItem(test.id, test.name, testUri); |
| currentTest.range = asRange(test.range); |
| children.push(currentTest); |
| currentSuite?.children.add(currentTest); |
| } |
| } |
| }); |
| if (testExecution) { |
| parentTests.forEach((val, key) => { |
| const item = this.testController.createTestItem(key.id, key.label, key.uri); |
| item.range = key.range; |
| item.children.replace(val); |
| currentSuite?.children.add(item); |
| }); |
| } else { |
| currentSuite.children.replace(children); |
| } |
| } |
| |
| subTestName(item: TestItem, test: TestCase): string | undefined { |
| if (test.id.startsWith(item.id)) { |
| let label = test.name; |
| if (label.startsWith(item.label)) { |
| label = label.slice(item.label.length).trim(); |
| } |
| return label; |
| } else { |
| const regexp = new RegExp(item.id.replace(/[-[\]{}()*+?.,\\^$|\s]/g, '\\$&').replace(/#\w*/g, '\\S*')); |
| if (regexp.test(test.id)) { |
| return test.name; |
| } |
| } |
| return undefined; |
| } |
| |
| selectParent(parents: Map<TestItem, string>): {test: TestItem, label: string} | undefined { |
| let ret: {test: TestItem, label: string} | undefined = undefined; |
| parents.forEach((label, parentTest) => { |
| if (ret) { |
| if (parentTest.id.replace(/#\w*/g, '').length > ret.test.id.replace(/#\w*/g, '').length) { |
| ret = {test: parentTest, label}; |
| } |
| } else { |
| ret = {test: parentTest, label}; |
| } |
| }); |
| return ret; |
| } |
| } |