refactor: Adopt to the new VS Code tests API (#1257)

This commit is contained in:
Sheng Chen 2021-08-03 13:49:53 +08:00 коммит произвёл GitHub
Родитель 66f1154a7e
Коммит fda8aff830
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
118 изменённых файлов: 2883 добавлений и 6469 удалений

8
.github/workflows/build.yml поставляемый
Просмотреть файл

@ -40,8 +40,8 @@ jobs:
- name: Build OSGi bundle
run: npm run build-plugin
- name: Test extension
run: DISPLAY=:99 npm test
# - name: Test extension
# run: DISPLAY=:99 npm test
- name: Print language server Log if job failed
if: ${{ failure() }}
@ -107,8 +107,8 @@ jobs:
- name: Build OSGi bundle
run: npm run build-plugin
- name: Test extension
run: npm test
# - name: Test extension
# run: npm test
- name: Print language server Log if job failed
if: ${{ failure() }}

2
.gitignore поставляемый
Просмотреть файл

@ -53,3 +53,5 @@ resources/templates/css/**
resources/templates/js/**
resources/templates/fonts/**
dist
**/vscode.d.ts
**/vscode.proposed.d.ts

Просмотреть файл

@ -4,6 +4,10 @@ All notable changes to the "vscode-java-test" extension will be documented in th
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## 0.31.0
### Changed
- Adopted new [VS Code testing API](https://github.com/microsoft/vscode/issues/107467). For more details, please refer to the [README page](https://github.com/microsoft/vscode-java-test/blob/main/README.md).
## 0.30.1
### Fixed
- [Bugs fixed](https://github.com/microsoft/vscode-java-test/issues?q=is%3Aissue+is%3Aclosed+label%3Abug+milestone%3A0.30.1)

Просмотреть файл

@ -35,8 +35,8 @@ If you are interested in writing code to fix issues, please check the following
### Overview
The extension has three major modules, which are listed as follow:
- The extension client written in TypeScript - UI logic mostly
- [The Java Test Plugin](https://github.com/Microsoft/vscode-java-test/tree/master/java-extension/com.microsoft.java.test.plugin) written in Java - Inspect the Java project
- [The Java Test Runner](https://github.com/Microsoft/vscode-java-test/tree/master/java-extension/com.microsoft.java.test.runner) written in Java - An executable jar to running the test cases
- [The Java Test Plugin](https://github.com/Microsoft/vscode-java-test/tree/main/java-extension/com.microsoft.java.test.plugin) written in Java - Inspect the Java project
- [The Java Test Runner](https://github.com/Microsoft/vscode-java-test/tree/main/java-extension/com.microsoft.java.test.runner) written in Java - An executable jar to running the test cases
### Setup
1. Fork and clone the repository: `git clone git@github.com:Microsoft/vscode-java-test.git`
@ -52,7 +52,7 @@ The extension has three major modules, which are listed as follow:
### Debugging
1. Hit `F5` (or run `Launch Extension` in the debug viewlet) to launch the extension in debug mode
> This will open a new VS Code window as a debug session. Open a Java project folder and let the extension be activated, then you can debug it.
2. If you want to debug the Java Test Plugin, run [Debug Test Runner Java Plugin (Attach)](https://github.com/microsoft/vscode-java-test/blob/master/.vscode/launch.json) in the debug viewlet.
2. If you want to debug the Java Test Plugin, run [Debug Test Runner Java Plugin (Attach)](https://github.com/microsoft/vscode-java-test/blob/main/.vscode/launch.json) in the debug viewlet.
> Note: If the Java code is changed by you, please run `npm run build-plugin` before you start debugging, the output jars will be generated in the folder `server/`.

Просмотреть файл

@ -3,11 +3,11 @@
> Run and debug Java test cases in Visual Studio Code
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/master/resources/logo.png" width="128" height="128" alt="">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/resources/logo.png" width="128" height="128" alt="">
</p>
<p align="center">
<a href="https://github.com/microsoft/vscode-java-test/actions?query=workflow%3ACI+branch%3Amaster">
<img src="https://img.shields.io/github/workflow/status/microsoft/vscode-java-test/CI/master?style=flat-square" alt="">
<a href="https://github.com/microsoft/vscode-java-test/actions?query=workflow%3ACI+branch%3Amain">
<img src="https://img.shields.io/github/workflow/status/microsoft/vscode-java-test/CI/main?style=flat-square" alt="">
</a>
<a href="https://lgtm.com/projects/g/microsoft/vscode-java-test/alerts/?mode=list">
<img src="https://img.shields.io/lgtm/alerts/g/microsoft/vscode-java-test.svg?style=flat-square" alt="">
@ -36,108 +36,100 @@ The [Java Test Runner](https://marketplace.visualstudio.com/items?itemName=vscja
- Customize test configurations
- View test report
- View tests in Test Explorer
- Show test logs
## Requirements
- JDK (version 11 or later)
- VS Code (version 1.44.0 or later)
- VS Code (version 1.59.0 or later)
- [Language Support for Java by Red Hat](https://marketplace.visualstudio.com/items?itemName=redhat.java)
- [Debugger for Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-debug)
## Quickstart
![Run/debug JUnit test](demo/demo.gif)
### Getting Started for JUnit 5
Please refer to [Getting Started](https://junit.org/junit5/docs/current/user-guide/#overview-getting-started) from the JUnit 5's official document for getting started guide.
> Note: You can use [junit-platform-console-standalone.jar](https://search.maven.org/search?q=g:org.junit.platform%20AND%20a:junit-platform-console-standalone) in projects that manually manage their dependencies similar to the [plain-old JAR known from JUnit 4](https://github.com/junit-team/junit4/wiki/Download-and-Install#plain-old-jar).
### Getting Started for JUnit 4
Please refer to [Download and Install](https://github.com/junit-team/junit4/wiki/Download-and-Install) from the JUnit 4's official document for the getting started guide.
### Getting Started for TestNG
Please refer to [TestNG Docs](https://testng.org/doc/) from the TestNG's official document for getting started guide.
## Features
### Run/Debug Test Cases
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/master/demo/run_codelens.png" style="border-radius: 15px" alt="Run Code Lens"/>
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/demo/editor-decoration.png" alt="Run/Debug Test Cases"/>
</p>
- The extension will generate `Run Test` and `Debug Test` shortcuts (also known as Code Lens) above the class and method definition. Simply click on them will start running or debugging the target test cases.
> Note: If you cannot see the Code Lens in your editor, please refer to this [issue comment](https://github.com/Microsoft/vscode-java-test/issues/470#issuecomment-444681714) as a workaround.
- The extension will generate shortcuts (the green play button) on the left side of the class and method definition. To run the target test cases, simply click on the green play button. You can also right click on it to see more options.
---
### Test Explorer
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/master/demo/run_explorer.png" style="border-radius: 15px" alt="Run Explorer"/>
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/demo/test_explorer.png" alt="Test Explorer"/>
</p>
- The Test Explorer is the place to show all the test cases in your project. You can also run/debug your test cases from here.
- Click the node in the Test Explorer will navigate to the location of the source code.
> Note: If the Test Explorer is empty, please refer to this [issue comment](https://github.com/Microsoft/vscode-java-test/issues/470#issuecomment-444681714) as a workaround.
- The Test Explorer is the place to show all the test cases in your workspace. You can also run/debug your test cases from here.
---
### Customize Test Configurations
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/master/demo/configuration.png" style="border-radius: 15px" alt="Customize Test Configurations"/>
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/demo/configuration.png" alt="Customize Test Configurations"/>
</p>
- Sometimes you may want to customize the configuration for running the test cases. To achieve this, you can add it into your workspace settings under the section: `java.test.config`.
- Sometimes you may want to customize the configuration to run your test cases. To achieve this, you can add the configuration into your workspace settings under the section: `java.test.config`.
> Note: More details can be found [here](https://github.com/Microsoft/vscode-java-test/wiki/Run-with-Configuration).
---
### View Test Report
### View Test Result
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/master/demo/status_bar.png" style="border-radius: 15px" alt="Status Bar"/>
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/demo/test_report.png" alt="View Test Result"/>
</p>
- After running/debugging the test cases, the status bar will show the final results. Simply click on it to show the Test Report.
- You can also click the ✔️ or ❌ mark in Code Lens to open the Test Report.
- After running/debugging the test cases, the state of the related test items will be updated in both editor decoration and test explorer.
- You can trigger the command `Test: Peek Output` to peek the result view.
- You can click on the links in the stack trace to navigate to the source location.
### VS Code Embedded Commands for Testing
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/master/demo/report_navigate.png" style="border-radius: 15px" alt="Status Bar"/>
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/demo/command_palette.png" alt="VS Code Embedded Commands for Testing"/>
</p>
- You can navigate to the source location of the target test case by clicking the navigate button.
> Note: You can use `java.test.report.showAfterExecution` to configure whether to automatically show the test report after execution. By default, it will be shown when there are failed tests.
There are other VS Code embedded commands for testing, which can be found by searching `Test:` in the Command Palette.
## Settings
| Setting Name | Description | Default Value |
|---|---|---|
| `java.test.report.position` | Specify where to show the test report. Supported values are: `sideView`, `currentView`. | `sideView` |
| `java.test.report.showAfterExecution` | Specify if the test report will automatically be shown after execution. Supported values are: `always`, `onFailure`, `never`. | `onFailure` |
| `java.test.editor.enableShortcuts` | Specify whether to show the Code Lenses in editor or not. | `true` |
| `java.test.log.level` | Specify the level of the test logs. Supported values are: `error`, `info`, `verbose`. | `info` |
| `java.test.config` | Specify the configuration for the test cases to run with. [More details](https://aka.ms/java-test-config). | `{}` |
| `java.test.defaultConfig` | Specify the name of the default test configuration. | `""` |
### VS Code Embedded Settings for Testing
<p align="center">
<img src="https://raw.githubusercontent.com/Microsoft/vscode-java-test/main/demo/settings.png" alt="VS Code Embedded Settings for Testing"/>
</p>
There are some other VS Code embedded settings for testing, which can be found by searching `testing` in the Settings view.
## Project Setup
### JUnit 5
Please refer to [Getting Started](https://junit.org/junit5/docs/current/user-guide/#overview-getting-started) from the JUnit 5's official document for getting started documentation.
> Note: If your project does not use build tools(Maven/Gradle/...), please make sure [junit-platform-console-standalone.jar](https://search.maven.org/search?q=g:org.junit.platform%20AND%20a:junit-platform-console-standalone) is on your project classpath.
### JUnit 4
Please refer to [Download and Install](https://github.com/junit-team/junit4/wiki/Download-and-Install) from the JUnit 4's official document for the getting started documentation.
### TestNG
Please refer to [TestNG Docs](https://testng.org/doc/) from the TestNG's official document for getting started documentation.
## FAQ
If you meet any problem when using the extension, please refer to the [FAQ](https://github.com/microsoft/vscode-java-test/wiki/FAQ) to check if there is an answer to your problem.
If you meet any problem when using the extension, please refer to the [FAQ](https://github.com/microsoft/vscode-java-test/wiki/FAQ) and our [issue list](https://github.com/microsoft/vscode-java-test/issues) to check if there is an answer to your problem.
## Contributing and Feedback
If you are interested in providing feedback or contributing directly to the code base, please check the document [Contributing to Java Test Runner](https://github.com/Microsoft/vscode-java-test/blob/master/CONTRIBUTING.md), which covers the following parts:
- [Questions and Feedback](https://github.com/Microsoft/vscode-java-test/blob/master/CONTRIBUTING.md#questions-and-feedback)
- [Reporting Issues](https://github.com/Microsoft/vscode-java-test/blob/master/CONTRIBUTING.md#reporting-issues)
- [Contributing Fixes](https://github.com/Microsoft/vscode-java-test/blob/master/CONTRIBUTING.md#contributing-fixes)
If you are interested in providing feedback or contributing directly to the code base, please check the document [Contributing to Java Test Runner](https://github.com/Microsoft/vscode-java-test/blob/main/CONTRIBUTING.md), which covers the following parts:
- [Questions and Feedback](https://github.com/Microsoft/vscode-java-test/blob/main/CONTRIBUTING.md#questions-and-feedback)
- [Reporting Issues](https://github.com/Microsoft/vscode-java-test/blob/main/CONTRIBUTING.md#reporting-issues)
- [Contributing Fixes](https://github.com/Microsoft/vscode-java-test/blob/main/CONTRIBUTING.md#contributing-fixes)
## License

Двоичные данные
demo/command_palette.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 13 KiB

Двоичные данные
demo/configuration.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 16 KiB

После

Ширина:  |  Высота:  |  Размер: 16 KiB

Двоичные данные
demo/demo.gif

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.6 MiB

Двоичные данные
demo/editor-decoration.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Двоичные данные
demo/report_navigate.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 11 KiB

Двоичные данные
demo/run_codelens.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 10 KiB

Двоичные данные
demo/run_explorer.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 17 KiB

Двоичные данные
demo/settings.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 22 KiB

Двоичные данные
demo/status_bar.png

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 9.3 KiB

Двоичные данные
demo/test_explorer.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 15 KiB

Двоичные данные
demo/test_report.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 40 KiB

Просмотреть файл

@ -2,12 +2,4 @@
// Licensed under the MIT license.
export { activate, deactivate } from './src/extension';
export * from './src/codelens/TestCodeLensProvider';
export * from './src/codelens/TestCodeLensController';
export * from './src/runners/models';
export * from './src/testResultManager';
export * from './src/protocols';
export * from './src/utils/commandUtils';
export * from './src/testFileWatcher';
export * from './src/runners/runnerScheduler';
export * from './src/provider/testSourceProvider';

Просмотреть файл

@ -4,10 +4,8 @@
const gulp = require('gulp');
const cp = require('child_process');
const tslint = require('gulp-tslint');
const sass = require('gulp-sass')(require('sass'));
const path = require('path');
const fs = require('fs');
const remoteSrc = require('gulp-remote-src');
const serverDir = path.join(__dirname, 'java-extension');
const resourceDir = path.join(__dirname, 'resources');
@ -54,24 +52,6 @@ gulp.task('tslint', (done) => {
gulp.task('lint', gulp.series('tslint'));
// Test report resources
gulp.task('sass', (done) => {
gulp.src(['resources/templates/scss/*.scss'])
.pipe(sass())
.pipe(gulp.dest('resources/templates/css'));
done();
});
gulp.task('download-resources', (done) => {
remoteSrc(['jquery-3.5.1.slim.min.js'], { base: 'https://code.jquery.com/' })
.pipe(gulp.dest(path.join(resourceDir, 'templates', 'js')));
remoteSrc(['bootstrap.bundle.min.js'], { base: 'https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/' })
.pipe(gulp.dest(path.join(resourceDir, 'templates', 'js')));
done();
});
gulp.task('build-resources', gulp.series('sass', 'download-resources'));
function isWin() {
return /^win/.test(process.platform);
}

Просмотреть файл

@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.java.test</groupId>
<artifactId>parent</artifactId>
<version>0.30.1</version>
<version>0.31.0</version>
</parent>
<groupId>com.microsoft.java.test</groupId>
<artifactId>test-runner-build-tools</artifactId>

Просмотреть файл

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<site>
<bundle id="com.microsoft.java.test.plugin" version="0.30.1"/>
<bundle id="com.microsoft.java.test.plugin" version="0.31.0"/>
</site>

Просмотреть файл

@ -4,7 +4,7 @@
<parent>
<artifactId>parent</artifactId>
<groupId>com.microsoft.java.test</groupId>
<version>0.30.1</version>
<version>0.31.0</version>
</parent>
<artifactId>com.microsoft.java.test.plugin.site</artifactId>
<packaging>eclipse-repository</packaging>

Просмотреть файл

@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: com.microsoft.java.test.plugin
Bundle-SymbolicName: com.microsoft.java.test.plugin;singleton:=true
Bundle-Version: 0.30.1
Bundle-Version: 0.31.0
Bundle-Activator: com.microsoft.java.test.plugin.util.JUnitPlugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.eclipse.jdt.core,

Просмотреть файл

@ -4,12 +4,13 @@
<extension point="org.eclipse.jdt.ls.core.delegateCommandHandler">
<delegateCommandHandler class="com.microsoft.java.test.plugin.handler.TestDelegateCommandHandler">
<command id="vscode.java.test.get.testpath" />
<command id="vscode.java.test.search.items" />
<command id="vscode.java.test.search.items.all" />
<command id="vscode.java.test.search.codelens" />
<command id="vscode.java.test.search.location" />
<command id="vscode.java.test.junit.argument" />
<command id="vscode.java.test.generateTests" />
<command id="vscode.java.test.findJavaProjects" />
<command id="vscode.java.test.findTestPackagesAndTypes" />
<command id="vscode.java.test.findDirectTestChildrenForClass" />
<command id="vscode.java.test.findTestTypesAndMethods" />
<command id="vscode.java.test.resolvePath" />
</delegateCommandHandler>
</extension>
</plugin>

Просмотреть файл

@ -5,7 +5,7 @@
<parent>
<groupId>com.microsoft.java.test</groupId>
<artifactId>parent</artifactId>
<version>0.30.1</version>
<version>0.31.0</version>
</parent>
<artifactId>com.microsoft.java.test.plugin</artifactId>
<packaging>eclipse-plugin</packaging>

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2019 Microsoft Corporation and others.
* Copyright (c) 2017-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -26,12 +26,14 @@ import java.util.List;
public class TestDelegateCommandHandler implements IDelegateCommandHandler {
private static final String GET_TEST_SOURCE_PATH = "vscode.java.test.get.testpath";
private static final String SEARCH_TEST_ITEMS = "vscode.java.test.search.items";
private static final String SEARCH_TEST_ALL_ITEMS = "vscode.java.test.search.items.all";
private static final String SEARCH_TEST_CODE_LENS = "vscode.java.test.search.codelens";
private static final String SEARCH_TEST_LOCATION = "vscode.java.test.search.location";
private static final String RESOLVE_JUNIT_ARGUMENT = "vscode.java.test.junit.argument";
private static final String GENERATE_TESTS = "vscode.java.test.generateTests";
private static final String FIND_JAVA_PROJECT = "vscode.java.test.findJavaProjects";
private static final String FIND_PACKAGES_AND_TYPES = "vscode.java.test.findTestPackagesAndTypes";
private static final String FIND_DIRECT_CHILDREN_FOR_CLASS = "vscode.java.test.findDirectTestChildrenForClass";
private static final String FIND_TYPES_AND_METHODS = "vscode.java.test.findTestTypesAndMethods";
private static final String RESOLVE_PATH = "vscode.java.test.resolvePath";
private static final String FIND_TEST_LOCATION = "vscode.java.test.findTestLocation";
@Override
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor monitor) throws Exception {
@ -42,18 +44,22 @@ public class TestDelegateCommandHandler implements IDelegateCommandHandler {
switch (commandId) {
case GET_TEST_SOURCE_PATH:
return ProjectTestUtils.listTestSourcePaths(arguments, monitor);
case SEARCH_TEST_ITEMS:
return TestSearchUtils.searchTestItems(arguments, monitor);
case SEARCH_TEST_ALL_ITEMS:
return TestSearchUtils.searchAllTestItems(arguments, monitor);
case SEARCH_TEST_CODE_LENS:
return TestSearchUtils.searchCodeLens(arguments, monitor);
case SEARCH_TEST_LOCATION:
return TestSearchUtils.searchLocation(arguments, monitor);
case RESOLVE_JUNIT_ARGUMENT:
return JUnitLaunchUtils.resolveLaunchArgument(arguments, monitor);
case GENERATE_TESTS:
return TestGenerationUtils.generateTests(arguments, monitor);
case FIND_JAVA_PROJECT:
return TestSearchUtils.findJavaProjects(arguments, monitor);
case FIND_PACKAGES_AND_TYPES:
return TestSearchUtils.findTestPackagesAndTypes(arguments, monitor);
case FIND_DIRECT_CHILDREN_FOR_CLASS:
return TestSearchUtils.findDirectTestChildrenForClass(arguments, monitor);
case FIND_TYPES_AND_METHODS:
return TestSearchUtils.findTestTypesAndMethods(arguments, monitor);
case RESOLVE_PATH:
return TestSearchUtils.resolvePath(arguments, monitor);
case FIND_TEST_LOCATION:
return TestSearchUtils.findTestLocation(arguments, monitor);
default:
throw new UnsupportedOperationException(
String.format("Java test plugin doesn't support the command '%s'.", commandId));

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019 Microsoft Corporation and others.
* Copyright (c) 2019-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,11 +11,10 @@
package com.microsoft.java.test.plugin.launchers;
import com.microsoft.java.test.plugin.util.JUnitPlugin;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.internal.core.LaunchConfiguration;
import org.eclipse.debug.internal.core.LaunchConfigurationInfo;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
@ -84,26 +83,21 @@ class JUnitLaunchConfigurationInfo extends LaunchConfigurationInfo {
final Element root = parser.parse(source).getDocumentElement();
initializeFromXML(root);
} catch (ParserConfigurationException | SAXException | IOException | CoreException e) {
// do nothing
throw new CoreException(new Status(IStatus.ERROR, "com.microsoft.java.test.plugin.launchers",
"Failed to load JUnit launch configuration", e));
JUnitPlugin.logException("Failed to load JUnit launch configuration.", e);
}
}
}
class TestInfo {
public String mainType = "";
public String testContainer = "";
public String testKind = "";
public String testName = "";
public String[] testNames;
public IProject project;
public Map<String, String> toValueMap() {
final Map<String, String> valueMap = new HashMap<>();
valueMap.put("testContainer", testContainer);
valueMap.put("testName", testName);
valueMap.put("testKind", testKind);
valueMap.put("mainType", mainType);
return valueMap;
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019 Microsoft Corporation and others.
* Copyright (c) 2019-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,25 +11,30 @@
package com.microsoft.java.test.plugin.launchers;
import com.microsoft.java.test.plugin.launchers.JUnitLaunchUtils.Argument;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.util.JUnitPlugin;
import com.microsoft.java.test.plugin.util.TestSearchUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.Launch;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;
import org.eclipse.jdt.internal.junit.JUnitMessages;
import org.eclipse.jdt.internal.junit.Messages;
import org.eclipse.jdt.internal.junit.launcher.ITestKind;
import org.eclipse.jdt.internal.junit.launcher.JUnitLaunchConfigurationConstants;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.launching.VMRunnerConfiguration;
import java.io.BufferedWriter;
@ -42,16 +47,23 @@ import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class JUnitLaunchConfigurationDelegate extends org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate {
private boolean fIsHierarchicalPackage;
private Argument args;
private static final Set<String> testNameArgs = Set.of("-test", "-classNames", "-packageNameFile", "-testNameFile");
public JUnitLaunchConfigurationDelegate(Argument args) {
super();
this.args = args;
}
public JUnitLaunchArguments getJUnitLaunchArguments(ILaunchConfiguration configuration, String mode,
boolean isHierarchicalPackage, IProgressMonitor monitor) throws CoreException {
fIsHierarchicalPackage = isHierarchicalPackage;
IProgressMonitor monitor) throws CoreException {
final ILaunch launch = new Launch(configuration, mode, null);
// TODO: Make the getVMRunnerConfiguration() in super class protected.
@ -70,88 +82,17 @@ public class JUnitLaunchConfigurationDelegate extends org.eclipse.jdt.junit.laun
launchArguments.classpath = config.getClassPath();
launchArguments.modulepath = config.getModulepath();
launchArguments.vmArguments = getVmArguments(config);
launchArguments.programArguments = config.getProgramArguments();
launchArguments.programArguments = parseParameters(config.getProgramArguments());
// The JUnit 5 launcher only supports run a single package, here we add all the sub-package names
// to the package name file as a workaround
if (isHierarchicalPackage &&
TestKindRegistry.JUNIT5_TEST_KIND_ID.equals(getTestRunnerKind(configuration).getId())) {
appendPackageNames(launchArguments.programArguments, configuration);
}
return launchArguments;
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException |
InvocationTargetException e) {
JUnitPlugin.logException("failed to resolve the classpath.", e);
return null;
} finally {
fIsHierarchicalPackage = false;
}
}
/*
* Override the super implementation when it is launched in hierarchical mode and starts from
* the package level
*
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#evaluateTests(
* org.eclipse.debug.core.ILaunchConfiguration, org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
protected IMember[] evaluateTests(ILaunchConfiguration configuration, IProgressMonitor monitor)
throws CoreException {
if (!fIsHierarchicalPackage) {
return super.evaluateTests(configuration, monitor);
}
final IPackageFragment testPackage = getTestPackage(configuration);
if (testPackage == null) {
return super.evaluateTests(configuration, monitor);
}
final IPackageFragment[] packages = JavaElementUtil.getPackageAndSubpackages(testPackage);
final HashSet<IType> result = new HashSet<>();
final ITestKind testKind = getTestRunnerKind(configuration);
for (final IPackageFragment packageFragment : packages) {
testKind.getFinder().findTestsInContainer(packageFragment, result, monitor);
}
if (result.isEmpty()) {
final String msg = Messages.format(JUnitMessages.JUnitLaunchConfigurationDelegate_error_notests_kind,
testKind.getDisplayName());
abort(msg, null, IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
}
return result.toArray(new IMember[result.size()]);
}
private IPackageFragment getTestPackage(ILaunchConfiguration configuration) throws CoreException {
final String containerHandle = configuration.getAttribute(
JUnitLaunchConfigurationConstants.ATTR_TEST_CONTAINER, "");
if (containerHandle.length() != 0) {
final IJavaElement element = JavaCore.create(containerHandle);
if (element == null || !element.exists()) {
abort(JUnitMessages.JUnitLaunchConfigurationDelegate_error_input_element_deosn_not_exist, null,
IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_MAIN_TYPE);
}
if (element instanceof IPackageFragment) {
return (IPackageFragment) element;
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jdt.junit.launcher.JUnitLaunchConfigurationDelegate#getTestRunnerKind(
* org.eclipse.debug.core.ILaunchConfiguration)
*/
private ITestKind getTestRunnerKind(ILaunchConfiguration configuration) {
ITestKind testKind = JUnitLaunchConfigurationConstants.getTestRunnerKind(configuration);
if (testKind.isNull()) {
testKind = TestKindRegistry.getDefault().getKind(TestKindRegistry.JUNIT4_TEST_KIND_ID);
}
return testKind;
}
private String[] getVmArguments(VMRunnerConfiguration config) {
final List<String> vmArgs = new ArrayList<>();
vmArgs.addAll(Arrays.asList(config.getVMArguments()));
@ -167,31 +108,98 @@ public class JUnitLaunchConfigurationDelegate extends org.eclipse.jdt.junit.laun
}
/**
* JUnit5's runner will run packages defined in a file, we can add more packages into that file when it's
* run from hierarchical mode to let the runner run test in all the sub-packages.
* To re-calculate the parameters to the test runner, this is because the argument resolved by Eclipse only supports
* run single package/class, but its test runner supports to run multiple test items in a test session,
* so we update the parameters here to leverage this capability.
* @param programArguments
* @return
* @throws CoreException
*/
private void appendPackageNames(String[] programArguments, ILaunchConfiguration configuration) {
private String[] parseParameters(String[] programArguments) throws CoreException {
final List<String> arguments = new LinkedList<>();
for (int i = 0; i < programArguments.length; i++) {
if ("-packageNameFile".equals(programArguments[i]) && i + 1 < programArguments.length) {
final String packageNameFilePath = programArguments[i + 1];
final File file = new File(packageNameFilePath);
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file),
StandardCharsets.UTF_8))) {
final IPackageFragment testPackage = getTestPackage(configuration);
if (testPackage == null) {
return;
}
final IPackageFragment[] packages = JavaElementUtil.getPackageAndSubpackages(testPackage);
for (final IPackageFragment pkg : packages) {
bw.write(pkg.getElementName());
bw.newLine();
}
} catch (IOException | CoreException e) {
// do nothing
if (testNameArgs.contains(programArguments[i])) {
while (i + 1 < programArguments.length && !programArguments[i + 1].startsWith("-")) {
i++;
}
} else {
arguments.add(programArguments[i]);
while (i + 1 < programArguments.length && !programArguments[i + 1].startsWith("-")) {
arguments.add(programArguments[++i]);
}
return;
}
}
addTestItemArgs(arguments);
return arguments.toArray(new String[arguments.size()]);
}
private void addTestItemArgs(List<String> arguments) throws CoreException {
if (this.args.testLevel == TestLevel.CLASS) {
final String fileName = createTestNamesFile(this.args.testNames);
arguments.add("-testNameFile");
arguments.add(fileName);
} else if (this.args.testLevel == TestLevel.METHOD) {
arguments.add("-test");
final IMethod method = (IMethod) JavaCore.create(this.args.testNames[0]);
String testName = method.getElementName();
if (this.args.testKind == TestKind.JUnit5 && method.getParameters().length > 0) {
final ICompilationUnit unit = method.getCompilationUnit();
if (unit == null) {
throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR,
"Cannot get compilation unit of method" + method.getElementName(), null)); //$NON-NLS-1$
}
final CompilationUnit root = (CompilationUnit) TestSearchUtils.parseToAst(unit,
false /*fromCache*/, new NullProgressMonitor());
final String key = method.getKey();
ASTNode methodDeclaration = root.findDeclaringNode(key);
if (methodDeclaration == null) {
// fallback to find it according to source range
methodDeclaration = NodeFinder.perform(root, method.getSourceRange().getOffset(),
method.getSourceRange().getLength(), unit);
}
if (!(methodDeclaration instanceof MethodDeclaration)) {
throw new CoreException(new Status(IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR,
"Cannot get method declaration of method" + method.getElementName(), null)); //$NON-NLS-1$
}
final List<String> parameters = new LinkedList<>();
for (final Object obj : ((MethodDeclaration) methodDeclaration).parameters()) {
if (obj instanceof SingleVariableDeclaration) {
final ITypeBinding paramTypeBinding = ((SingleVariableDeclaration) obj)
.getType().resolveBinding();
if (paramTypeBinding.isParameterizedType()) {
parameters.add(paramTypeBinding.getBinaryName());
} else {
parameters.add(paramTypeBinding.getQualifiedName());
}
}
}
if (parameters.size() > 0) {
testName += "(" + String.join(",", parameters) + ")";
}
}
arguments.add(method.getDeclaringType().getFullyQualifiedName() + ':' + testName);
}
}
private String createTestNamesFile(String[] testNames) throws CoreException {
try {
final File file = File.createTempFile("testNames", ".txt"); //$NON-NLS-1$ //$NON-NLS-2$
file.deleteOnExit();
try (BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream(file), StandardCharsets.UTF_8));) {
for (final String testName : testNames) {
bw.write(testName.substring(testName.indexOf("@") + 1));
bw.newLine();
}
}
return file.getAbsolutePath();
} catch (IOException e) {
throw new CoreException(new Status(
IStatus.ERROR, JUnitPlugin.PLUGIN_ID, IStatus.ERROR, "", e)); //$NON-NLS-1$
}
}
public static class JUnitLaunchArguments {

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2019 Microsoft Corporation and others.
* Copyright (c) 2019-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -15,49 +15,30 @@ import com.google.gson.Gson;
import com.microsoft.java.test.plugin.launchers.JUnitLaunchConfigurationDelegate.JUnitLaunchArguments;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.util.TestItemUtils;
import com.microsoft.java.test.plugin.util.TestSearchUtils;
import com.microsoft.java.test.plugin.util.JUnitPlugin;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.handlers.JsonRpcHelpers;
import org.eclipse.lsp4j.Position;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
@ -69,6 +50,14 @@ public class JUnitLaunchUtils {
private JUnitLaunchUtils() {}
/**
* Resolve the arguments to launch the Eclipse test runner
* @param arguments
* @param monitor
* @return
* @throws URISyntaxException
* @throws CoreException
*/
public static JUnitLaunchArguments resolveLaunchArgument(List<Object> arguments, IProgressMonitor monitor)
throws URISyntaxException, CoreException {
final Gson gson = new Gson();
@ -78,27 +67,16 @@ public class JUnitLaunchUtils {
info.testKind = getEclipseTestKind(args.testKind);
final IJavaProject javaProject = ProjectUtils.getJavaProject(args.project);
final IJavaProject javaProject = ProjectUtils.getJavaProject(args.projectName);
if (javaProject == null || !javaProject.exists()) {
throw new RuntimeException("Failed to get the project with name: " + args.project);
JUnitPlugin.logError("Failed to get the project: " + args.projectName);
throw new RuntimeException("Failed to get the project: " + args.projectName);
}
info.project = javaProject.getProject();
if (args.scope == TestLevel.ROOT || args.scope == TestLevel.FOLDER) {
info.testContainer = StringEscapeUtils.escapeXml(javaProject.getHandleIdentifier());
} else {
final File file = Paths.get(new URI(args.uri)).toFile();
if (args.scope == TestLevel.PACKAGE && file.isDirectory()) {
parseConfigurationInfoForContainer(info, args);
} else if ((args.scope == TestLevel.CLASS || args.scope == TestLevel.METHOD) && file.isFile()) {
parseConfigurationInfoForClass(info, args, monitor);
} else {
throw new RuntimeException("The resource: " + file.getPath() + " is not testable.");
}
}
info.testContainer = StringEscapeUtils.escapeXml(javaProject.getHandleIdentifier());
final ILaunchConfiguration configuration = new JUnitLaunchConfiguration("JUnit Launch Configuration", info);
final JUnitLaunchConfigurationDelegate delegate = new JUnitLaunchConfigurationDelegate();
final JUnitLaunchConfigurationDelegate delegate = new JUnitLaunchConfigurationDelegate(args);
if (monitor.isCanceled()) {
return null;
@ -109,7 +87,7 @@ public class JUnitLaunchUtils {
return resolveTestNGLaunchArguments(configuration, javaProject, delegate);
}
return delegate.getJUnitLaunchArguments(configuration, "run", args.isHierarchicalPackage, monitor);
return delegate.getJUnitLaunchArguments(configuration, "run", monitor);
}
public static void addOverrideDependencies(List<String> vmArgs, String dependencies) {
@ -119,107 +97,6 @@ public class JUnitLaunchUtils {
}
}
private static void parseConfigurationInfoForClass(TestInfo info, Argument args,
IProgressMonitor monitor) throws JavaModelException {
final ICompilationUnit cu = JDTUtils.resolveCompilationUnit(args.uri);
if (cu == null) {
throw new RuntimeException("Cannot resolve compilation unit from: " + args.uri);
}
for (final IType type : cu.getAllTypes()) {
if (type.getFullyQualifiedName().equals(args.fullName)) {
info.mainType = args.fullName;
info.testName = parseTestName(args, cu, monitor);
break;
}
}
if (info.mainType == null) {
throw new RuntimeException("Failed to find class '" + args.fullName + "'");
}
}
private static String parseTestName(Argument args, ICompilationUnit cu,
IProgressMonitor monitor) throws JavaModelException {
String testName = StringUtils.isEmpty(args.testName) ? "" : args.testName;
// JUnit 5's methods need to have parameter information to launch
if (args.testKind == TestKind.JUnit5 && args.scope == TestLevel.METHOD) {
final ASTNode unit = TestSearchUtils.parseToAst(cu, true /*fromCache*/, monitor);
if (unit == null) {
return "";
}
final int startOffset = JsonRpcHelpers.toOffset(cu.getOpenable(), args.start.getLine(),
args.start.getCharacter());
final int endOffset = JsonRpcHelpers.toOffset(cu.getOpenable(), args.end.getLine(),
args.end.getCharacter());
// the offsets point to the range of SimpleName
ASTNode methodDeclaration = NodeFinder.perform(unit, startOffset, endOffset - startOffset, cu);
while (!(methodDeclaration instanceof MethodDeclaration)) {
methodDeclaration = methodDeclaration.getParent();
}
final List<String> parameters = new LinkedList<>();
for (final Object obj : ((MethodDeclaration) methodDeclaration).parameters()) {
if (obj instanceof SingleVariableDeclaration) {
final ITypeBinding paramTypeBinding = ((SingleVariableDeclaration) obj).getType().resolveBinding();
if (paramTypeBinding.isParameterizedType()) {
parameters.add(paramTypeBinding.getBinaryName());
} else {
parameters.add(paramTypeBinding.getQualifiedName());
}
}
}
if (parameters.size() > 0) {
testName += String.format("(%s)", String.join(",", parameters));
}
}
return testName;
}
private static void parseConfigurationInfoForContainer(TestInfo info, Argument args) throws URISyntaxException,
JavaModelException {
final IContainer[] targetContainers = ResourcesPlugin.getWorkspace().getRoot()
.findContainersForLocationURI(new URI(args.uri));
if (targetContainers == null || targetContainers.length == 0) {
throw new RuntimeException("Cannot find resource containers from: " + args.uri);
}
// For multi-module scenario, findContainersForLocationURI API may return a container array,
// need put the result from the nearest project in front.
Arrays.sort(targetContainers, (Comparator<IContainer>) (IContainer a, IContainer b) -> {
return a.getFullPath().toPortableString().length() - b.getFullPath().toPortableString().length();
});
IJavaElement targetElement = null;
for (final IContainer container : targetContainers) {
targetElement = JavaCore.create(container);
if (targetElement != null) {
final IJavaProject javaProject = targetElement.getJavaProject();
if (javaProject == null) {
continue;
}
info.project = javaProject.getProject();
break;
}
}
if (targetElement == null) {
throw new RuntimeException("Cannot resolve valid element from: " + args.uri);
}
if (TestItemUtils.DEFAULT_PACKAGE_NAME.equals(args.fullName)) {
if (targetElement instanceof IPackageFragmentRoot) {
final IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) targetElement;
for (final IJavaElement child : packageRoot.getChildren()) {
if (child instanceof IPackageFragment && ((IPackageFragment) child).isDefaultPackage()) {
targetElement = (IPackageFragment) child;
break;
}
}
}
}
info.testContainer = StringEscapeUtils.escapeXml(targetElement.getHandleIdentifier());
}
private static JUnitLaunchArguments resolveTestNGLaunchArguments(ILaunchConfiguration configuration,
IJavaProject javaProject, JUnitLaunchConfigurationDelegate delegate) throws CoreException {
final IRuntimeClasspathEntry[] unresolved = JavaRuntime.computeUnresolvedRuntimeClasspath(configuration);
@ -311,14 +188,9 @@ public class JUnitLaunchUtils {
}
class Argument {
public String uri;
public String fullName;
public String testName;
public String project;
public TestLevel scope;
public String projectName;
public TestLevel testLevel;
public TestKind testKind;
public Position start;
public Position end;
public boolean isHierarchicalPackage;
public String[] testNames;
}
}

Просмотреть файл

@ -0,0 +1,174 @@
/*******************************************************************************
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.test.plugin.model;
import org.eclipse.lsp4j.Range;
import java.util.ArrayList;
import java.util.List;
public class JavaTestItem {
private String id;
private String label;
private String fullName;
private List<JavaTestItem> children;
private TestLevel testLevel;
private TestKind testKind;
private String projectName;
private String uri;
private Range range;
private String jdtHandler;
public JavaTestItem() {}
public JavaTestItem(String displayName, String fullName, String project, String uri,
Range range, TestLevel level, TestKind kind) {
this.label = displayName;
this.fullName = fullName;
this.testLevel = level;
this.testKind = kind;
this.projectName = project;
this.uri = uri;
this.range = range;
if (level.equals(TestLevel.PROJECT)) {
this.id = fullName;
} else {
this.id = project + "@" + fullName;
}
}
public Range getRange() {
return range;
}
public void setRange(Range range) {
this.range = range;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getJdtHandler() {
return jdtHandler;
}
public void setJdtHandler(String jdtHandler) {
this.jdtHandler = jdtHandler;
}
public String getId() {
return id;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public List<JavaTestItem> getChildren() {
return children;
}
public void setChildren(List<JavaTestItem> children) {
this.children = children;
}
public TestLevel getTestLevel() {
return testLevel;
}
public void setTestLevel(TestLevel testLevel) {
this.testLevel = testLevel;
}
public TestKind getTestKind() {
return testKind;
}
public void setTestKind(TestKind testKind) {
this.testKind = testKind;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public void addChild(JavaTestItem child) {
if (this.children == null) {
this.children = new ArrayList<>();
}
this.children.add(child);
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final JavaTestItem other = (JavaTestItem) obj;
if (id == null) {
if (other.id != null) {
return false;
}
} else if (!id.equals(other.id)){
return false;
}
return true;
}
}

Просмотреть файл

@ -1,54 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.test.plugin.model;
public class SearchTestItemParams {
private TestLevel level;
private String uri;
private String fullName;
private boolean isHierarchicalPackage;
public TestLevel getLevel() {
return level;
}
public boolean isHierarchicalPackage() {
return isHierarchicalPackage;
}
public void setHierarchicalPackage(boolean isHierarchicalPackage) {
this.isHierarchicalPackage = isHierarchicalPackage;
}
public void setLevel(TestLevel level) {
this.level = level;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
}

Просмотреть файл

@ -1,118 +0,0 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*******************************************************************************/
package com.microsoft.java.test.plugin.model;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Range;
import java.util.ArrayList;
import java.util.List;
public class TestItem {
private String id;
private String displayName;
private String fullName;
private List<String> children;
private TestLevel level;
private TestKind kind;
private String project;
private Location location;
public TestItem(String displayName, String fullName, String uri, String project,
Range range, TestLevel level, TestKind kind) {
this.displayName = displayName;
this.fullName = fullName;
this.level = level;
this.kind = kind;
this.project = project;
this.location = new Location(uri, range);
this.id = project + "@" + fullName;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public List<String> getChildren() {
return children;
}
public void setChildren(List<String> children) {
this.children = children;
}
public TestLevel getLevel() {
return level;
}
public void setLevel(TestLevel level) {
this.level = level;
}
public TestKind getKind() {
return kind;
}
public void setKind(TestKind kind) {
this.kind = kind;
}
public String getProject() {
return project;
}
public void setProject(String project) {
this.project = project;
}
public void addChild(String child) {
if (this.children == null) {
this.children = new ArrayList<>();
}
this.children.add(child);
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* Copyright (c) 2018-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -18,14 +18,41 @@ public enum TestLevel {
ROOT,
@SerializedName("1")
FOLDER,
WORKSPACE,
@SerializedName("2")
PACKAGE,
WORKSPACE_FOLDER,
@SerializedName("3")
CLASS,
PROJECT,
@SerializedName("4")
PACKAGE,
@SerializedName("5")
CLASS,
@SerializedName("6")
METHOD;
public static TestLevel fromInteger(Integer i) {
switch(i) {
case 0:
return ROOT;
case 1:
return WORKSPACE;
case 2:
return WORKSPACE_FOLDER;
case 3:
return PROJECT;
case 4:
return PACKAGE;
case 5:
return CLASS;
case 6:
return METHOD;
default:
return null;
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* Copyright (c) 2018-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,16 +11,10 @@
package com.microsoft.java.test.plugin.searcher;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.util.TestFrameworkUtils;
import com.microsoft.java.test.plugin.util.TestItemUtils;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
public abstract class BaseFrameworkSearcher implements TestFrameworkSearcher {
@ -47,10 +41,4 @@ public abstract class BaseFrameworkSearcher implements TestFrameworkSearcher {
}
return false;
}
@Override
public TestItem parseTestItem(IMethodBinding methodBinding) throws JavaModelException {
return TestItemUtils.constructTestItem((IMethod) methodBinding.getJavaElement(),
TestLevel.METHOD, this.getTestKind());
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Microsoft Corporation and others.
* Copyright (c) 2017-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,13 +11,11 @@
package com.microsoft.java.test.plugin.searcher;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.util.TestItemUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
@ -26,12 +24,9 @@ import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.internal.junit.launcher.JUnit4TestFinder;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class JUnit4TestSearcher extends BaseFrameworkSearcher {
@ -72,20 +67,14 @@ public class JUnit4TestSearcher extends BaseFrameworkSearcher {
}
@Override
public TestItem[] findTestsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException {
final Map<String, TestItem> result = new HashMap<>();
public Set<IType> findTestItemsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException {
final Set<IType> types = new HashSet<>();
JUNIT4_TEST_FINDER.findTestsInContainer(element, types, monitor);
for (final IType type : types) {
final TestItem item = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit);
item.setChildren(Arrays.stream(type.getMethods())
.map(m -> m.getJavaProject().getProject().getName() + "@" +
TestItemUtils.parseTestItemFullName(m, TestLevel.METHOD))
.collect(Collectors.toList())
);
result.put(item.getId(), item);
try {
JUNIT4_TEST_FINDER.findTestsInContainer(element, types, monitor);
} catch (OperationCanceledException e) {
return Collections.emptySet();
}
return result.values().toArray(new TestItem[0]);
return types;
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Microsoft Corporation and others.
* Copyright (c) 2017-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,14 +11,12 @@
package com.microsoft.java.test.plugin.searcher;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.util.TestFrameworkUtils;
import com.microsoft.java.test.plugin.util.TestItemUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
@ -29,12 +27,9 @@ import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.internal.junit.launcher.JUnit5TestFinder;
import org.eclipse.jdt.internal.junit.launcher.TestKindRegistry;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class JUnit5TestSearcher extends BaseFrameworkSearcher {
@ -74,24 +69,6 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher {
return this.findAnnotation(methodBinding.getAnnotations(), this.getTestMethodAnnotations());
}
@Override
public TestItem parseTestItem(IMethodBinding methodBinding) throws JavaModelException {
final TestItem item = super.parseTestItem(methodBinding);
// deal with @DisplayName
for (final IAnnotationBinding annotation : methodBinding.getAnnotations()) {
if (annotation == null) {
continue;
}
if (matchesName(annotation.getAnnotationType(), DISPLAY_NAME_ANNOTATION_JUNIT5)) {
item.setDisplayName((String) annotation.getAllMemberValuePairs()[0].getValue());
break;
}
}
return item;
}
@Override
public boolean findAnnotation(IAnnotationBinding[] annotations, String[] annotationNames) {
for (final IAnnotationBinding annotation : annotations) {
@ -119,24 +96,6 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher {
return JUNIT5_TEST_FINDER.isTest(type);
}
@Override
public TestItem[] findTestsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException {
final Map<String, TestItem> result = new HashMap<>();
final Set<IType> types = new HashSet<>();
JUNIT5_TEST_FINDER.findTestsInContainer(element, types, monitor);
for (final IType type : types) {
final TestItem item = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit5);
item.setChildren(Arrays.stream(type.getMethods())
.map(m ->m.getJavaProject().getProject().getName() + "@" +
TestItemUtils.parseTestItemFullName(m, TestLevel.METHOD))
.collect(Collectors.toList())
);
result.put(item.getId(), item);
}
return result.values().toArray(new TestItem[0]);
}
private boolean matchesName(ITypeBinding annotationType, String annotationName) {
return TestFrameworkUtils.isEquivalentAnnotationType(annotationType, annotationName);
}
@ -163,4 +122,15 @@ public class JUnit5TestSearcher extends BaseFrameworkSearcher {
return false;
}
@Override
public Set<IType> findTestItemsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException {
final Set<IType> types = new HashSet<>();
try {
JUNIT5_TEST_FINDER.findTestsInContainer(element, types, monitor);
} catch (OperationCanceledException e) {
return Collections.emptySet();
}
return types;
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Microsoft Corporation and others.
* Copyright (c) 2017-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,7 +11,6 @@
package com.microsoft.java.test.plugin.searcher;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import org.eclipse.core.runtime.CoreException;
@ -22,6 +21,8 @@ import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import java.util.Set;
public interface TestFrameworkSearcher {
TestKind getTestKind();
@ -36,7 +37,5 @@ public interface TestFrameworkSearcher {
boolean findAnnotation(IAnnotationBinding[] annotations, String[] annotationNames);
TestItem parseTestItem(IMethodBinding methodBinding) throws JavaModelException;
TestItem[] findTestsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException;
Set<IType> findTestItemsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException;
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* Copyright (c) 2018-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,14 +11,12 @@
package com.microsoft.java.test.plugin.searcher;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.util.TestItemUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaElement;
@ -49,13 +47,10 @@ import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class TestNGTestSearcher extends BaseFrameworkSearcher {
@ -171,25 +166,6 @@ public class TestNGTestSearcher extends BaseFrameworkSearcher {
return false;
}
@Override
public TestItem[] findTestsInContainer(final IJavaElement element, final IProgressMonitor monitor)
throws CoreException {
final Map<String, TestItem> result = new HashMap<>();
final Set<IType> types = new HashSet<>();
findTestsInContainer(element, types, monitor);
for (final IType type : types) {
final TestItem item = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.TestNG);
item.setChildren(
Arrays.stream(type.getMethods())
.map(m -> m.getJavaProject().getProject().getName() + "@" +
TestItemUtils.parseTestItemFullName(m, TestLevel.METHOD))
.collect(Collectors.toList()));
result.put(item.getId(), item);
}
return result.values().toArray(new TestItem[0]);
}
/*
* (non-Javadoc)
*
@ -240,6 +216,17 @@ public class TestNGTestSearcher extends BaseFrameworkSearcher {
pm.done();
}
}
@Override
public Set<IType> findTestItemsInContainer(IJavaElement element, IProgressMonitor monitor) throws CoreException {
final Set<IType> types = new HashSet<>();
try {
this.findTestsInContainer(element, types, monitor);
} catch (OperationCanceledException e) {
return Collections.emptySet();
}
return types;
}
}
/*

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017 Microsoft Corporation and others.
* Copyright (c) 2017-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -64,14 +64,39 @@ public final class ProjectTestUtils {
return resultList;
}
public static List<TestSourcePath> getTestSourcePaths(IJavaProject project) throws JavaModelException {
final List<TestSourcePath> paths = new LinkedList<>();
public static List<IClasspathEntry> getTestEntries(IJavaProject project) throws JavaModelException {
// Ignore default project
if (ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getProject().getName())) {
return Collections.emptyList();
}
final List<IClasspathEntry> entries = new LinkedList<>();
for (final IClasspathEntry entry : project.getRawClasspath()) {
// Ignore default project
if (ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getProject().getName())) {
if (entry.getEntryKind() != ClasspathEntry.CPE_SOURCE) {
continue;
}
if (isTestEntry(entry)) {
entries.add(entry);
continue;
}
// Always return true Eclipse & invisible project
if (ProjectUtils.isGeneralJavaProject(project.getProject())) {
entries.add(entry);
}
}
return entries;
}
public static List<TestSourcePath> getTestSourcePaths(IJavaProject project) throws JavaModelException {
// Ignore default project
if (ProjectsManager.DEFAULT_PROJECT_NAME.equals(project.getProject().getName())) {
return Collections.emptyList();
}
final List<TestSourcePath> paths = new LinkedList<>();
for (final IClasspathEntry entry : project.getRawClasspath()) {
if (entry.getEntryKind() != ClasspathEntry.CPE_SOURCE) {
continue;
}

Просмотреть файл

@ -11,24 +11,14 @@
package com.microsoft.java.test.plugin.util;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.searcher.JUnit4TestSearcher;
import com.microsoft.java.test.plugin.searcher.JUnit5TestSearcher;
import com.microsoft.java.test.plugin.searcher.TestFrameworkSearcher;
import com.microsoft.java.test.plugin.searcher.TestNGTestSearcher;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class TestFrameworkUtils {
@ -37,70 +27,20 @@ public class TestFrameworkUtils {
public static final TestFrameworkSearcher JUNIT5_TEST_SEARCHER = new JUnit5TestSearcher();
public static final TestFrameworkSearcher TESTNG_TEST_SEARCHER = new TestNGTestSearcher();
public static final TestFrameworkSearcher[] FRAMEWORK_SEARCHERS = new TestFrameworkSearcher[] {
JUNIT4_TEST_SEARCHER, JUNIT5_TEST_SEARCHER, TESTNG_TEST_SEARCHER };
public static void findTestItemsInTypeBinding(ITypeBinding typeBinding, List<TestItem> result,
TestItem parentClassTestItem, IProgressMonitor monitor) throws JavaModelException {
if (monitor.isCanceled()) {
return;
}
final List<TestFrameworkSearcher> searchers = new ArrayList<>();
final IType type = (IType) typeBinding.getJavaElement();
for (final TestFrameworkSearcher searcher : FRAMEWORK_SEARCHERS) {
if (CoreTestSearchEngine.isAccessibleClass(type, searcher.getJdtTestKind())) {
searchers.add(searcher);
}
}
if (searchers.size() == 0) {
return;
}
final List<TestItem> testMethods = new LinkedList<>();
final List<String> testMethodIds = new LinkedList<>();
for (final IMethodBinding methodBinding : typeBinding.getDeclaredMethods()) {
for (final TestFrameworkSearcher searcher : searchers) {
if (searcher.isTestMethod(methodBinding)) {
final TestItem methodItem = searcher.parseTestItem(methodBinding);
testMethods.add(methodItem);
testMethodIds.add(methodItem.getId());
break;
}
}
}
TestItem classItem = null;
if (testMethods.size() > 0) {
result.addAll(testMethods);
classItem = TestItemUtils.constructTestItem((IType) typeBinding.getJavaElement(),
TestLevel.CLASS);
classItem.setChildren(testMethodIds);
classItem.setKind(testMethods.get(0).getKind());
result.add(classItem);
} else {
if (JUNIT4_TEST_SEARCHER.isTestClass(type)) {
// to handle @RunWith classes
classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit);
result.add(classItem);
} else if (JUNIT5_TEST_SEARCHER.isTestClass(type)) {
// to handle @Nested and @Testable classes
classItem = TestItemUtils.constructTestItem(type, TestLevel.CLASS, TestKind.JUnit5);
result.add(classItem);
}
}
// set the class item as the child of its declaring type
if (classItem != null && parentClassTestItem != null) {
parentClassTestItem.addChild(classItem.getId());
}
for (final ITypeBinding childTypeBinding : typeBinding.getDeclaredTypes()) {
findTestItemsInTypeBinding(childTypeBinding, result, classItem, monitor);
}
}
public static boolean isEquivalentAnnotationType(ITypeBinding annotationType, String annotationName) {
return annotationType != null && Objects.equals(annotationType.getQualifiedName(), annotationName);
}
public static TestFrameworkSearcher getSearcherByTestKind(TestKind kind) {
switch (kind) {
case JUnit:
return JUNIT4_TEST_SEARCHER;
case JUnit5:
return JUNIT5_TEST_SEARCHER;
case TestNG:
return TESTNG_TEST_SEARCHER;
default:
return null;
}
}
}

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* Copyright (c) 2018-2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,7 +11,7 @@
package com.microsoft.java.test.plugin.util;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.JavaTestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
@ -22,7 +22,9 @@ import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels;
import org.eclipse.lsp4j.Range;
@SuppressWarnings("restriction")
@ -30,39 +32,42 @@ public class TestItemUtils {
public static final String DEFAULT_PACKAGE_NAME = "<Default Package>";
public static TestItem constructTestItem(IJavaElement element, TestLevel level) throws JavaModelException {
return constructTestItem(element, level, null);
}
public static TestItem constructTestItem(IJavaElement element, TestLevel level, TestKind kind)
public static JavaTestItem constructJavaTestItem(IJavaElement element, TestLevel level, TestKind kind)
throws JavaModelException {
final String displayName;
final String fullName;
if (element instanceof IPackageFragment && ((IPackageFragment) element).isDefaultPackage()) {
displayName = DEFAULT_PACKAGE_NAME;
fullName = DEFAULT_PACKAGE_NAME;
} else {
displayName = element.getElementName();
fullName = parseTestItemFullName(element, level);
displayName = JavaElementLabels.getElementLabel(element, JavaElementLabels.ALL_DEFAULT);
}
final String fullName = parseFullName(element, level);
final String uri = JDTUtils.getFileURI(element.getResource());
final Range range = parseTestItemRange(element);
Range range = null;
if (level == TestLevel.CLASS || level == TestLevel.METHOD) {
range = parseTestItemRange(element);
}
final String projectName = element.getJavaProject().getProject().getName();
return new TestItem(displayName, fullName, uri, projectName, range, level, kind);
final JavaTestItem result = new JavaTestItem(displayName, fullName, projectName, uri, range, level, kind);
result.setJdtHandler(element.getHandleIdentifier());
return result;
}
public static Range parseTestItemRange(IJavaElement element) throws JavaModelException {
if (element instanceof ISourceReference) {
// getSourceRange() is not used here because we want the Code Lens appear above the
// method name, not above the annotation.
final ISourceRange range = ((ISourceReference) element).getNameRange();
return JDTUtils.toRange(element.getOpenable(), range.getOffset(), range.getLength());
final ISourceRange sourceRange = ((ISourceReference) element).getSourceRange();
final ISourceRange nameRange = ((ISourceReference) element).getNameRange();
// get the code range excluding the comment part
if (SourceRange.isAvailable(sourceRange) && SourceRange.isAvailable(nameRange)) {
return JDTUtils.toRange(element.getOpenable(), nameRange.getOffset(),
sourceRange.getLength() - nameRange.getOffset() + sourceRange.getOffset());
}
}
return new Range();
return null;
}
public static String parseTestItemFullName(IJavaElement element, TestLevel level) {
public static String parseFullName(IJavaElement element, TestLevel level) {
switch (level) {
case CLASS:
final IType type = (IType) element;

Просмотреть файл

@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2018 Microsoft Corporation and others.
* Copyright (c) 2021 Microsoft Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@ -11,21 +11,23 @@
package com.microsoft.java.test.plugin.util;
import com.google.gson.Gson;
import com.microsoft.java.test.plugin.model.SearchTestItemParams;
import com.microsoft.java.test.plugin.model.TestItem;
import com.microsoft.java.test.plugin.model.JavaTestItem;
import com.microsoft.java.test.plugin.model.TestKind;
import com.microsoft.java.test.plugin.model.TestLevel;
import com.microsoft.java.test.plugin.provider.TestKindProvider;
import com.microsoft.java.test.plugin.searcher.TestFrameworkSearcher;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
@ -33,87 +35,520 @@ import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.manipulation.CoreASTProvider;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.dom.IASTSharedValues;
import org.eclipse.jdt.core.search.TypeNameMatch;
import org.eclipse.jdt.core.search.TypeNameMatchRequestor;
import org.eclipse.jdt.internal.junit.util.CoreTestSearchEngine;
import org.eclipse.jdt.ls.core.internal.JDTUtils;
import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin;
import org.eclipse.jdt.ls.core.internal.ProjectUtils;
import org.eclipse.jdt.ls.core.internal.ResourceUtils;
import org.eclipse.jdt.ls.core.internal.handlers.DocumentLifeCycleHandler;
import org.eclipse.jdt.ls.core.internal.managers.ProjectsManager;
import org.eclipse.lsp4j.Location;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import java.util.stream.Collectors;
@SuppressWarnings("restriction")
public class TestSearchUtils {
/**
* Method to search the Code Lenses
*
* @param arguments contains the URI of the file to search the Code Lens.
* @param monitor
* @throws OperationCanceledException
* @throws InterruptedException
* @throws JavaModelException
* List all the Java Projects in the given workspace folder
*/
public static List<TestItem> searchCodeLens(List<Object> arguments, IProgressMonitor monitor)
throws OperationCanceledException, InterruptedException, JavaModelException {
final List<TestItem> resultList = new LinkedList<>();
public static List<JavaTestItem> findJavaProjects(List<Object> arguments, IProgressMonitor monitor) {
if (arguments == null || arguments.size() == 0) {
return resultList;
return Collections.emptyList();
}
final String workspaceFolderUri = (String) arguments.get(0);
final IPath workspaceFolderPath = ResourceUtils.filePathFromURI(workspaceFolderUri);
if (workspaceFolderPath == null) {
JUnitPlugin.logError("Failed to parse workspace folder path from uri: " + workspaceFolderUri);
// todo: handle non-file scheme
return Collections.emptyList();
}
final List<IJavaProject> javaProjects = new LinkedList<>();
for (final IJavaProject project : ProjectUtils.getJavaProjects()) {
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
if (project.getProject().equals(JavaLanguageServerPlugin.getProjectsManager().getDefaultProject())) {
continue;
}
javaProjects.add(project);
}
final String uri = (String) arguments.get(0);
final List<JavaTestItem> resultList = new LinkedList<>();
for (final IJavaProject project : javaProjects) {
try {
resultList.add(TestItemUtils.constructJavaTestItem(project, TestLevel.PROJECT, TestKind.None));
} catch (JavaModelException e) {
JUnitPlugin.logError("Failed to parse project item: " + project.getElementName());
}
}
return resultList;
}
/**
* Return a list of test packages and types in the given project. The returned list has the following hierarchy:
* Package A
* Class A
* Nested Class A
* Class B
* Package B
* ...
*
* @param arguments argument list which contains the JDT handler ID of a Java project
* @param monitor monitor
* @throws CoreException
*/
public static List<JavaTestItem> findTestPackagesAndTypes(List<Object> arguments, IProgressMonitor monitor)
throws CoreException {
final String handlerId = (String) arguments.get(0);
final IJavaElement element = JavaCore.create(handlerId);
if (!(element instanceof IJavaProject)) {
JUnitPlugin.logError("failed to parse IJavaProject from JDT handler ID: " + handlerId);
return Collections.emptyList();
}
final IJavaProject javaProject = (IJavaProject) element;
final Map<String, JavaTestItem> testItemMapping = new HashMap<>();
final List<TestKind> testKinds = TestKindProvider.getTestKindsFromCache(javaProject);
for (final TestKind kind : testKinds) {
if (monitor != null && monitor.isCanceled()) {
return Collections.emptyList();
}
final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind);
final Set<IType> testTypes = new HashSet<>();
final List<IClasspathEntry> testEntries = ProjectTestUtils.getTestEntries(javaProject);
for (final IClasspathEntry entry : testEntries) {
final IPackageFragmentRoot[] packageRoots = javaProject.findPackageFragmentRoots(entry);
for (final IPackageFragmentRoot root : packageRoots) {
try {
testTypes.addAll(searcher.findTestItemsInContainer(root, monitor));
} catch (CoreException e) {
JUnitPlugin.logException("failed to search tests in: " + root.getElementName(), e);
}
}
}
for (final IType type : testTypes) {
JavaTestItem classItem = testItemMapping.get(type.getHandleIdentifier());
if (classItem == null) {
classItem = TestItemUtils.constructJavaTestItem(
type, TestLevel.CLASS, kind);
testItemMapping.put(classItem.getJdtHandler(), classItem);
} else {
// 1. We suppose a class can only use one test framework
// 2. If more accurate kind is available, use it.
if (classItem.getTestKind() == TestKind.JUnit5 && kind == TestKind.JUnit) {
classItem.setTestKind(TestKind.JUnit);
}
}
final IType declaringType = type.getDeclaringType();
if (declaringType == null) {
// it's a root type, we find its declaring package
final IPackageFragment packageFragment = type.getPackageFragment();
final String packageIdentifier = packageFragment.getHandleIdentifier();
JavaTestItem packageItem = testItemMapping.get(packageIdentifier);
if (packageItem == null) {
packageItem = TestItemUtils.constructJavaTestItem(
packageFragment, TestLevel.PACKAGE, TestKind.None);
testItemMapping.put(packageIdentifier, packageItem);
}
if (packageItem.getChildren() == null || !packageItem.getChildren().contains(classItem)) {
packageItem.addChild(classItem);
}
} else {
final String declaringTypeIdentifier = declaringType.getHandleIdentifier();
JavaTestItem declaringTypeItem = testItemMapping.get(declaringTypeIdentifier);
if (declaringTypeItem == null) {
declaringTypeItem = TestItemUtils.constructJavaTestItem(
declaringType, TestLevel.CLASS, kind);
testItemMapping.put(declaringTypeIdentifier, declaringTypeItem);
}
if (declaringTypeItem.getChildren() == null ||
!declaringTypeItem.getChildren().contains(classItem)) {
declaringTypeItem.addChild(classItem);
}
}
}
}
final List<JavaTestItem> result = new LinkedList<>();
for (final JavaTestItem item : testItemMapping.values()) {
if (item.getTestLevel() == TestLevel.PACKAGE) {
result.add(item);
}
}
return result;
}
/**
* Find the direct declared testable class and method for a given class
* @param arguments
* @param monitor
* @return
* @throws JavaModelException
* @throws OperationCanceledException
* @throws InterruptedException
*/
public static List<JavaTestItem> findDirectTestChildrenForClass(List<Object> arguments, IProgressMonitor monitor)
throws JavaModelException, OperationCanceledException, InterruptedException {
final String handlerId = (String) arguments.get(0);
// wait for the LS finishing updating
Job.getJobManager().join(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor);
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(uri);
final IType testType = (IType) JavaCore.create(handlerId);
if (testType == null) {
return Collections.emptyList();
}
final ICompilationUnit unit = testType.getCompilationUnit();
if (unit == null) {
return Collections.emptyList();
}
final List<TestKind> testKinds = TestKindProvider.getTestKindsFromCache(unit.getJavaProject());
final List<JavaTestItem> result = new LinkedList<>();
final CompilationUnit root = (CompilationUnit) parseToAst(unit, true /* fromCache */, monitor);
for (final IType type : unit.getAllTypes()) {
if (monitor != null && monitor.isCanceled()) {
return result;
}
final IType declaringType = type.getDeclaringType();
if (declaringType != null &&
declaringType.getFullyQualifiedName().equals(testType.getFullyQualifiedName())) {
for (final TestKind kind: testKinds) {
final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind);
if (searcher.isTestClass(type)) {
result.add(TestItemUtils.constructJavaTestItem(type, TestLevel.CLASS, kind));
break;
}
}
continue;
}
if (!type.getFullyQualifiedName().equals(testType.getFullyQualifiedName())) {
continue;
}
final ASTNode node = root.findDeclaringNode(type.getKey());
if (!(node instanceof TypeDeclaration)) {
continue;
}
final ITypeBinding binding = ((TypeDeclaration) node).resolveBinding();
if (binding == null) {
continue;
}
for (final IMethodBinding methodBinding : binding.getDeclaredMethods()) {
for (final TestKind kind: testKinds) {
final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind);
if (searcher.isTestMethod(methodBinding)) {
result.add(TestItemUtils.constructJavaTestItem(
(IMethod) methodBinding.getJavaElement(),
TestLevel.METHOD,
kind
));
}
}
}
}
return result;
}
/**
* Get all the test types and methods is the given file
* @param arguments Contains the target file's uri
* @param monitor Progress monitor
* @throws CoreException
* @throws OperationCanceledException
* @throws InterruptedException
*/
public static List<JavaTestItem> findTestTypesAndMethods(List<Object> arguments, IProgressMonitor monitor)
throws CoreException, OperationCanceledException, InterruptedException {
// todo: This method is somehow duplicated with findDirectTestChildrenForClass,
// considering merge them in the future.
final String uriString = (String) arguments.get(0);
// wait for the LS finishing updating
Job.getJobManager().join(DocumentLifeCycleHandler.DOCUMENT_LIFE_CYCLE_JOBS, monitor);
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(uriString);
if (unit == null) {
return Collections.emptyList();
}
final IType primaryType = unit.findPrimaryType();
if (!isJavaElementExist(unit) || primaryType == null || monitor.isCanceled()) {
return resultList;
if (primaryType == null) {
return Collections.emptyList();
}
final CompilationUnit root = (CompilationUnit) parseToAst(unit, true /* fromCache */, monitor);
if (root == null) {
return resultList;
return Collections.emptyList();
}
final List<TestKind> testKinds = TestKindProvider.getTestKindsFromCache(unit.getJavaProject());
final List<TestFrameworkSearcher> searchers = new LinkedList<>();
for (final TestKind kind : testKinds) {
final TestFrameworkSearcher searcher = TestFrameworkUtils.getSearcherByTestKind(kind);
if (searcher != null) {
searchers.add(searcher);
}
}
if (searchers.size() == 0) {
Collections.emptyList();
}
final ASTNode node = root.findDeclaringNode(primaryType.getKey());
if (!(node instanceof TypeDeclaration)) {
return resultList;
return Collections.emptyList();
}
final ITypeBinding binding = ((TypeDeclaration) node).resolveBinding();
if (binding == null) {
return resultList;
return Collections.emptyList();
}
TestFrameworkUtils.findTestItemsInTypeBinding(binding, resultList, null /* parentClassItem */, monitor);
final JavaTestItem fakeRoot = new JavaTestItem();
findTestItemsInTypeBinding(binding, fakeRoot, searchers, monitor);
return fakeRoot.getChildren();
}
return resultList;
private static void findTestItemsInTypeBinding(ITypeBinding typeBinding, JavaTestItem parentItem,
List<TestFrameworkSearcher> searchers, IProgressMonitor monitor) throws JavaModelException {
if (monitor.isCanceled()) {
return;
}
final IType type = (IType) typeBinding.getJavaElement();
final List<JavaTestItem> testMethods = new LinkedList<>();
searchers = searchers.stream().filter(s -> {
try {
return CoreTestSearchEngine.isAccessibleClass(type, s.getJdtTestKind());
} catch (JavaModelException e) {
return false;
}
}).collect(Collectors.toList());
for (final IMethodBinding methodBinding : typeBinding.getDeclaredMethods()) {
for (final TestFrameworkSearcher searcher : searchers) {
if (searcher.isTestMethod(methodBinding)) {
final JavaTestItem methodItem = TestItemUtils.constructJavaTestItem(
(IMethod) methodBinding.getJavaElement(),
TestLevel.METHOD,
searcher.getTestKind()
);
testMethods.add(methodItem);
break;
}
}
}
JavaTestItem classItem = null;
if (testMethods.size() > 0) {
classItem = TestItemUtils.constructJavaTestItem(type, TestLevel.CLASS, testMethods.get(0).getTestKind());
classItem.setChildren(testMethods);
} else {
if (TestFrameworkUtils.JUNIT4_TEST_SEARCHER.isTestClass(type)) {
// to handle @RunWith classes
classItem = TestItemUtils.constructJavaTestItem(type, TestLevel.CLASS, TestKind.JUnit);
} else if (TestFrameworkUtils.JUNIT5_TEST_SEARCHER.isTestClass(type)) {
// to handle @Nested and @Testable classes
classItem = TestItemUtils.constructJavaTestItem(type, TestLevel.CLASS, TestKind.JUnit5);
}
}
// set the class item as the child of its declaring type
if (classItem != null && parentItem != null) {
parentItem.addChild(classItem);
}
for (final ITypeBinding childTypeBinding : typeBinding.getDeclaredTypes()) {
findTestItemsInTypeBinding(childTypeBinding, classItem, searchers, monitor);
}
}
/**
* Given a file Uri, resolve its belonging project and package node in the test explorer, this is
* used to find the test item node when a test file is changed
* @param arguments A list containing a uri of the file
* @param monitor Progress monitor
* @throws JavaModelException
*/
public static List<JavaTestItem> resolvePath(List<Object> arguments, IProgressMonitor monitor)
throws JavaModelException {
final List<JavaTestItem> result = new LinkedList<>();
final String uriString = (String) arguments.get(0);
if (JavaCore.isJavaLikeFileName(uriString)) {
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(uriString);
if (unit == null) {
return Collections.emptyList();
}
final IJavaProject project = unit.getJavaProject();
if (project == null) {
return Collections.emptyList();
}
result.add(TestItemUtils.constructJavaTestItem(project, TestLevel.PROJECT, TestKind.None));
final IPackageFragment packageFragment = (IPackageFragment) unit.getParent();
if (packageFragment == null || !(packageFragment instanceof IPackageFragment)) {
return Collections.emptyList();
}
result.add(TestItemUtils.constructJavaTestItem(packageFragment, TestLevel.PACKAGE, TestKind.None));
}
return result;
}
/**
* Given the test item's full name, get its location. This is used to calculate the location for each test message.
*/
public static Location findTestLocation(List<Object> arguments, IProgressMonitor monitor)
throws JavaModelException {
final String fullName = (String) arguments.get(0);
if (StringUtils.isEmpty(fullName)) {
return null;
}
final int projectNameEnd = fullName.indexOf("@");
if (projectNameEnd < 0) {
return null;
}
final String projectName = fullName.substring(0, projectNameEnd);
if (StringUtils.isEmpty(projectName)) {
return null;
}
final IJavaProject javaProject = ProjectUtils.getJavaProject(projectName);
if (javaProject == null) {
return null;
}
final int methodStart = fullName.indexOf("#");
final String typeName;
final String methodName;
if (methodStart > 0) {
typeName = fullName.substring(projectNameEnd + 1, methodStart);
methodName = fullName.substring(methodStart + 1);
} else {
typeName = fullName.substring(projectNameEnd + 1);
methodName = null;
}
final IType type = findType(javaProject, typeName, monitor);
if (type == null) {
return null;
}
if (StringUtils.isEmpty(methodName)) {
return new Location(JDTUtils.getFileURI(type.getResource()), TestItemUtils.parseTestItemRange(type));
}
for (final IMethod method : type.getMethods()) {
if (methodName.equals(method.getElementName())) {
// TODO: handle the override method
return new Location(JDTUtils.getFileURI(method.getResource()),
TestItemUtils.parseTestItemRange(method));
}
}
return null;
}
protected static final IType findType(final IJavaProject project, String className, IProgressMonitor monitor) {
final IType[] result = { null };
final String dottedName = className.replace('$', '.'); // for nested classes...
try {
if (project != null) {
result[0] = internalFindType(project, dottedName, new HashSet<IJavaProject>(), monitor);
}
if (result[0] == null) {
final int lastDot = dottedName.lastIndexOf('.');
final TypeNameMatchRequestor nameMatchRequestor = new TypeNameMatchRequestor() {
@Override
public void acceptTypeNameMatch(TypeNameMatch match) {
result[0] = match.getType();
}
};
new SearchEngine().searchAllTypeNames(
lastDot >= 0 ? dottedName.substring(0, lastDot).toCharArray() : null,
SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE,
(lastDot >= 0 ? dottedName.substring(lastDot + 1) : dottedName).toCharArray(),
SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE,
IJavaSearchConstants.TYPE,
SearchEngine.createWorkspaceScope(),
nameMatchRequestor,
IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH,
monitor);
}
} catch (JavaModelException e) {
JUnitPlugin.log(e);
}
return result[0];
}
/**
* copied from org.eclipse.jdt.internal.junit.ui.OpenEditorAction.internalFindType()
*/
private static IType internalFindType(IJavaProject project, String className, Set<IJavaProject> visitedProjects,
IProgressMonitor monitor) throws JavaModelException {
try {
if (visitedProjects.contains(project)) {
return null;
}
monitor.beginTask("", 2); //$NON-NLS-1$
IType type = project.findType(className, new SubProgressMonitor(monitor, 1));
if (type != null) {
return type;
}
//fix for bug 87492: visit required projects explicitly to also find not exported types
visitedProjects.add(project);
final IJavaModel javaModel = project.getJavaModel();
final String[] requiredProjectNames = project.getRequiredProjectNames();
final IProgressMonitor reqMonitor = new SubProgressMonitor(monitor, 1);
reqMonitor.beginTask("", requiredProjectNames.length); //$NON-NLS-1$
for (final String requiredProjectName : requiredProjectNames) {
final IJavaProject requiredProject = javaModel.getJavaProject(requiredProjectName);
if (requiredProject.exists()) {
type = internalFindType(requiredProject, className, visitedProjects,
new SubProgressMonitor(reqMonitor, 1));
if (type != null) {
return type;
}
}
}
return null;
} finally {
monitor.done();
}
}
public static ASTNode parseToAst(final ICompilationUnit unit, final boolean fromCache,
@ -130,278 +565,11 @@ public class TestSearchUtils {
return null;
}
final ASTParser parser = ASTParser.newParser(IASTSharedValues.SHARED_AST_LEVEL);
final ASTParser parser = ASTParser.newParser(AST.JLS14);
parser.setSource(unit);
parser.setFocalPosition(0);
parser.setResolveBindings(true);
parser.setIgnoreMethodBodies(true);
return parser.createAST(monitor);
}
/**
* Method to expand the node in Test Explorer
*
* @param arguments {@link com.microsoft.java.test.plugin.model.SearchTestItemParams}
* @param monitor
* @throws OperationCanceledException
* @throws InterruptedException
* @throws URISyntaxException
* @throws JavaModelException
*/
public static List<TestItem> searchTestItems(final List<Object> arguments, final IProgressMonitor monitor)
throws OperationCanceledException, InterruptedException, URISyntaxException, JavaModelException {
final List<TestItem> resultList = new LinkedList<>();
if (arguments == null || arguments.size() == 0) {
return resultList;
}
final Gson gson = new Gson();
final SearchTestItemParams params = gson.fromJson((String) arguments.get(0), SearchTestItemParams.class);
switch (params.getLevel()) {
case FOLDER:
searchInFolder(resultList, params);
break;
case PACKAGE:
searchInPackage(resultList, params);
break;
case CLASS:
searchInClass(resultList, params, monitor);
break;
default:
break;
}
return resultList;
}
/**
* Method to get all the test items when running tests from Test Explorer
*
* @param arguments {@link com.microsoft.java.test.plugin.model.SearchTestItemParams}
* @param monitor
* @throws CoreException
* @throws InterruptedException
* @throws URISyntaxException
*/
public static List<TestItem> searchAllTestItems(List<Object> arguments, IProgressMonitor monitor)
throws CoreException, InterruptedException, URISyntaxException {
if (arguments == null || arguments.size() == 0) {
return Collections.emptyList();
}
final Gson gson = new Gson();
final SearchTestItemParams params = gson.fromJson((String) arguments.get(0), SearchTestItemParams.class);
if (params.getLevel() == TestLevel.METHOD) {
// unreachable code since the client will directly run the test when it's triggered from method level
throw new UnsupportedOperationException("Method level execution is not supported at server side.");
} else if (params.getLevel() == TestLevel.CLASS) {
final IJavaElement[] elements = getJavaElementForSearch(params);
if (elements == null) {
return Collections.emptyList();
}
final IType type = (IType) elements[0];
TestItem[] testItems = null;
if (TestFrameworkUtils.JUNIT4_TEST_SEARCHER.isTestClass(type)) {
testItems = TestFrameworkUtils.JUNIT4_TEST_SEARCHER.findTestsInContainer(type, monitor);
} else if (TestFrameworkUtils.JUNIT5_TEST_SEARCHER.isTestClass(type)) {
testItems = TestFrameworkUtils.JUNIT5_TEST_SEARCHER.findTestsInContainer(type, monitor);
} else if (TestFrameworkUtils.TESTNG_TEST_SEARCHER.isTestClass(type)) {
testItems = TestFrameworkUtils.TESTNG_TEST_SEARCHER.findTestsInContainer(type, monitor);
}
if (testItems != null) {
return Arrays.asList(testItems);
}
return Collections.emptyList();
} else {
final IJavaElement[] elements = getJavaElementForSearch(params);
final Map<IJavaProject, List<TestFrameworkSearcher>> javaProjectMapping = new HashMap<>();
final Set<IJavaProject> javaProjects = new HashSet<>();
for (final IJavaElement element : elements) {
javaProjects.add(element.getJavaProject());
}
final List<TestFrameworkSearcher> searchers = new LinkedList<>();
for (final IJavaProject javaProject : javaProjects) {
// We don't check JUnit 4 when JUnit 5 is available since it's backward compatible
if (javaProject.findType("org.junit.jupiter.api.Test") != null) {
searchers.add(TestFrameworkUtils.JUNIT5_TEST_SEARCHER);
} else if (javaProject.findType("org.junit.Test") != null) {
searchers.add(TestFrameworkUtils.JUNIT4_TEST_SEARCHER);
}
if (javaProject.findType("org.testng.annotations.Test") != null) {
searchers.add(TestFrameworkUtils.TESTNG_TEST_SEARCHER);
}
javaProjectMapping.put(javaProject, searchers);
}
final Map<String, TestItem> map = new HashMap<>();
for (final IJavaElement element : elements) {
for (final TestFrameworkSearcher searcher : javaProjectMapping.get(element.getJavaProject())) {
final TestItem[] items = searcher.findTestsInContainer(element, monitor);
Arrays.stream(items).forEach(item -> {
map.put(item.getId(), item);
});
}
}
return new ArrayList<>(map.values());
}
}
public static List<Location> searchLocation(List<Object> arguments, IProgressMonitor monitor) throws CoreException {
final List<Location> searchResult = new LinkedList<>();
if (arguments == null || arguments.size() == 0) {
throw new IllegalArgumentException("Invalid arguments to search the location.");
}
String searchString = ((String) arguments.get(0)).replaceAll("[$#]", ".");
int searchFor = IJavaSearchConstants.METHOD;
if (searchString.endsWith("<TestError>")) {
searchString = searchString.substring(0, searchString.indexOf("<TestError>") - 1);
searchFor = IJavaSearchConstants.CLASS;
}
final SearchPattern pattern = SearchPattern.createPattern(searchString, searchFor,
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_PATTERN_MATCH);
final IJavaProject[] projects = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot())
.getJavaProjects();
final IJavaSearchScope scope = SearchEngine.createJavaSearchScope(projects, IJavaSearchScope.SOURCES);
final SearchRequestor requestor = new SearchRequestor() {
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
final Object element = match.getElement();
if (element instanceof IMethod || element instanceof IType) {
final IJavaElement javaElement = (IJavaElement) element;
searchResult.add(new Location(JDTUtils.getFileURI(javaElement.getResource()),
TestItemUtils.parseTestItemRange(javaElement)));
}
}
};
new SearchEngine().search(pattern, new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() },
scope, requestor, monitor);
return searchResult;
}
private static boolean isInTestScope(IJavaElement element) throws JavaModelException {
final IJavaProject project = element.getJavaProject();
for (final IPath sourcePath : ProjectUtils.listSourcePaths(project)) {
if (!ProjectTestUtils.isTest(project, sourcePath, true /*containsGeneralProject*/)) {
continue;
}
if (sourcePath.isPrefixOf(element.getPath())) {
return true;
}
}
return false;
}
private static IJavaElement[] getJavaElementForSearch(SearchTestItemParams params) throws JavaModelException {
switch (params.getLevel()) {
case ROOT:
return Stream.of(ProjectUtils.getJavaProjects())
.filter(javaProject -> !ProjectsManager.DEFAULT_PROJECT_NAME
.equals(javaProject.getProject().getName()))
.toArray(IJavaProject[]::new);
case FOLDER:
final Set<IJavaProject> projectSet = ProjectTestUtils.parseProjects(params.getUri());
return projectSet.toArray(new IJavaElement[projectSet.size()]);
case PACKAGE:
final IJavaElement packageElement = resolvePackage(params.getUri(), params.getFullName());
return new IJavaElement[] { packageElement };
case CLASS:
final ICompilationUnit compilationUnit = JDTUtils.resolveCompilationUnit(params.getUri());
if (compilationUnit == null) {
return null;
}
for (final IType type : compilationUnit.getAllTypes()) {
if (Objects.equals(type.getFullyQualifiedName(), params.getFullName())) {
return new IJavaElement[] { type };
}
}
return null;
}
return new IJavaElement[] {};
}
private static void searchInFolder(List<TestItem> resultList, SearchTestItemParams params)
throws URISyntaxException, JavaModelException {
final Set<IJavaProject> projectSet = ProjectTestUtils.parseProjects(params.getUri());
for (final IJavaProject project : projectSet) {
for (final IPackageFragment packageFragment : project.getPackageFragments()) {
if (isInTestScope(packageFragment) && packageFragment.getCompilationUnits().length > 0) {
resultList.add(TestItemUtils.constructTestItem(packageFragment, TestLevel.PACKAGE));
}
}
}
}
private static void searchInPackage(List<TestItem> resultList, SearchTestItemParams params)
throws JavaModelException {
final IPackageFragment packageFragment = (IPackageFragment) resolvePackage(params.getUri(),
params.getFullName());
if (packageFragment == null) {
return;
}
for (final ICompilationUnit unit : packageFragment.getCompilationUnits()) {
for (final IType type : unit.getTypes()) {
resultList.add(TestItemUtils.constructTestItem(type, TestLevel.CLASS));
}
}
}
private static IJavaElement resolvePackage(String uriString, String fullName) throws JavaModelException {
final IFolder resource = (IFolder) JDTUtils.findResource(JDTUtils.toURI(uriString),
ResourcesPlugin.getWorkspace().getRoot()::findContainersForLocationURI);
final IJavaElement element = JavaCore.create(resource);
if (TestItemUtils.DEFAULT_PACKAGE_NAME.equals(fullName)) {
if (element instanceof IPackageFragmentRoot) {
final IPackageFragmentRoot packageRoot = (IPackageFragmentRoot) element;
for (final IJavaElement child : packageRoot.getChildren()) {
if (child instanceof IPackageFragment && ((IPackageFragment) child).isDefaultPackage()) {
return (IPackageFragment) child;
}
}
}
}
return element;
}
private static void searchInClass(List<TestItem> resultList, SearchTestItemParams params,
IProgressMonitor monitor) throws JavaModelException {
final ICompilationUnit unit = JDTUtils.resolveCompilationUnit(params.getUri());
final CompilationUnit root = (CompilationUnit) parseToAst(unit, false /* fromCache */, monitor);
for (final IType type : unit.getAllTypes()) {
if (type.getFullyQualifiedName().equals(params.getFullName())) {
final ASTNode node = root.findDeclaringNode(type.getKey());
if (!(node instanceof TypeDeclaration)) {
continue;
}
final ITypeBinding binding = ((TypeDeclaration) node).resolveBinding();
if (binding == null) {
continue;
}
TestFrameworkUtils.findTestItemsInTypeBinding(binding, resultList, null /* parentClassItem */, monitor);
resultList.removeIf(item -> item.getLevel() != TestLevel.METHOD ||
!item.getFullName().startsWith(params.getFullName() + "#"));
for (final IType innerType : type.getTypes()) {
resultList.add(TestItemUtils.constructTestItem(innerType, TestLevel.CLASS));
}
break;
}
}
}
private static boolean isJavaElementExist(IJavaElement element) {
return element != null && element.getResource() != null && element.getResource().exists();
}
}

Просмотреть файл

@ -4,7 +4,7 @@
<parent>
<groupId>com.microsoft.java.test</groupId>
<artifactId>parent</artifactId>
<version>0.30.1</version>
<version>0.31.0</version>
</parent>
<artifactId>com.microsoft.java.test.runner</artifactId>
<packaging>jar</packaging>

Просмотреть файл

@ -4,7 +4,7 @@
<groupId>com.microsoft.java.test</groupId>
<artifactId>parent</artifactId>
<name>${base.name} :: Parent</name>
<version>0.30.1</version>
<version>0.31.0</version>
<packaging>pom</packaging>
<properties>
<base.name>Java Test Runner</base.name>

1747
package-lock.json сгенерированный

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -1,11 +1,12 @@
{
"enableProposedApi": true,
"name": "vscode-java-test",
"displayName": "Java Test Runner",
"description": "%description%",
"repository": {
"url": "https://github.com/Microsoft/vscode-java-test"
},
"version": "0.30.1",
"version": "0.31.0",
"publisher": "vscjava",
"bugs": {
"url": "https://github.com/Microsoft/vscode-java-test/issues"
@ -22,7 +23,7 @@
],
"aiKey": "90c182a8-8dab-45d4-bfb8-1353eb55aa7f",
"engines": {
"vscode": "^1.56.0"
"vscode": "^1.59.0"
},
"categories": [
"Other"
@ -39,17 +40,12 @@
"workspaceContains:build.gradle",
"workspaceContains:.classpath",
"onCommand:java.test.editor.run",
"onCommand:java.test.editor.debug",
"onCommand:java.test.cancel",
"onCommand:java.test.show.report",
"onCommand:java.test.show.output",
"onCommand:java.test.open.log",
"onCommand:java.test.config.migrate"
"onCommand:java.test.editor.debug"
],
"main": "./main.js",
"contributes": {
"javaExtensions": [
"./server/com.microsoft.java.test.plugin-0.30.1.jar",
"./server/com.microsoft.java.test.plugin-0.31.0.jar",
"./server/org.eclipse.jdt.junit4.runtime_1.1.1200.v20200214-0716.jar",
"./server/org.eclipse.jdt.junit5.runtime_1.0.900.v20200513-0617.jar",
"./server/org.junit.jupiter.api_5.6.0.v20200203-2009.jar",
@ -65,18 +61,9 @@
"./server/org.junit.platform.suite.api_1.6.0.v20200203-2009.jar",
"./server/org.apiguardian_1.1.0.v20190826-0900.jar"
],
"views": {
"test": [
{
"id": "testExplorer",
"name": "Java",
"when": "java:testRunnerActivated"
}
]
},
"viewsWelcome": [
{
"view": "testExplorer",
"view": "testing",
"contents": "%contributes.viewsWelcome.inLightWeightMode%",
"when": "java:serverMode == LightWeight"
},
@ -94,52 +81,12 @@
"menus": {
"view/title": [
{
"command": "java.test.relaunch",
"when": "view == testExplorer && java:serverMode != LightWeight",
"group": "navigation@10"
},
{
"command": "java.test.explorer.runAll",
"when": "view == testExplorer && java:serverMode != LightWeight",
"group": "navigation@20"
},
{
"command": "java.test.explorer.debugAll",
"when": "view == testExplorer && java:serverMode != LightWeight",
"group": "navigation@30"
},
{
"command": "java.test.explorer.refresh",
"when": "view == testExplorer && java:serverMode != LightWeight",
"group": "navigation@40"
"command": "java.test.refreshExplorer",
"when": "view == workbench.view.testing",
"group": "zzz@zzz"
}
],
"view/item/context": [
{
"command": "java.test.explorer.run",
"when": "view == testExplorer && viewItem != UNTESTABLE_NODE",
"group": "testExplorer@0"
},
{
"command": "java.test.explorer.debug",
"when": "view == testExplorer && viewItem != UNTESTABLE_NODE",
"group": "testExplorer@1"
},
{
"command": "java.test.explorer.refresh",
"when": "view == testExplorer && viewItem != UNTESTABLE_NODE",
"group": "testExplorer@4"
},
{
"command": "java.test.explorer.run",
"when": "view == testExplorer && viewItem != UNTESTABLE_NODE",
"group": "inline@0"
},
{
"command": "java.test.explorer.debug",
"when": "view == testExplorer && viewItem != UNTESTABLE_NODE",
"group": "inline@1"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"when": "view == javaProjectExplorer && viewItem =~ /java:(type|package|packageRoot)(?=.*?\\b\\+uri\\b)(?=.*?\\b\\+test\\b)/",
@ -157,18 +104,6 @@
}
],
"commandPalette": [
{
"command": "java.test.explorer.run",
"when": "false"
},
{
"command": "java.test.explorer.debug",
"when": "false"
},
{
"command": "java.test.explorer.refresh",
"when": "false"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"when": "false"
@ -177,18 +112,6 @@
"command": "java.test.debugFromJavaProjectExplorer",
"when": "false"
},
{
"command": "java.test.config.migrate",
"when": "java:hasDeprecatedTestConfig"
},
{
"command": "java.test.explorer.runAll",
"when": "java:serverMode != LightWeight"
},
{
"command": "java.test.explorer.debugAll",
"when": "java:serverMode != LightWeight"
},
{
"command": "java.test.editor.run",
"when": "java:serverMode != LightWeight"
@ -196,71 +119,19 @@
{
"command": "java.test.editor.debug",
"when": "java:serverMode != LightWeight"
},
{
"command": "java.test.relaunch",
"when": "java:serverMode != LightWeight"
},
{
"command": "java.test.cancel",
"when": "java:serverMode != LightWeight"
},
{
"command": "java.test.show.report",
"when": "java:serverMode != LightWeight"
}
]
},
"commands": [
{
"command": "java.test.show.output",
"title": "%contributes.commands.java.test.show.output.title%",
"category": "Java"
},
{
"command": "java.test.open.log",
"title": "%contributes.commands.java.test.open.log.title%",
"category": "Java"
},
{
"command": "java.test.explorer.run",
"title": "%contributes.commands.java.test.explorer.run.title%",
"icon": "$(play)",
"category": "Java"
},
{
"command": "java.test.explorer.debug",
"title": "%contributes.commands.java.test.explorer.debug.title%",
"icon": "$(debug-alt-small)",
"category": "Java"
},
{
"command": "java.test.explorer.runAll",
"title": "%contributes.commands.java.test.explorer.runAll.title%",
"icon": "$(run-all)",
"category": "Java"
},
{
"command": "java.test.runFromJavaProjectExplorer",
"title": "%contributes.commands.java.test.runFromJavaProjectExplorer%",
"title": "%contributes.commands.java.test.runFromJavaProjectExplorer.title%",
"icon": "$(play)",
"category": "Java"
},
{
"command": "java.test.debugFromJavaProjectExplorer",
"title": "%contributes.commands.java.test.debugFromJavaProjectExplorer%",
"category": "Java"
},
{
"command": "java.test.explorer.debugAll",
"title": "%contributes.commands.java.test.explorer.debugAll.title%",
"icon": "$(debug-alt)",
"category": "Java"
},
{
"command": "java.test.relaunch",
"title": "%contributes.commands.java.test.relaunch.title%",
"icon": "$(debug-restart)",
"title": "%contributes.commands.java.test.debugFromJavaProjectExplorer.title%",
"category": "Java"
},
{
@ -274,80 +145,14 @@
"category": "Java"
},
{
"command": "java.test.cancel",
"title": "%contributes.commands.java.test.cancel.title%",
"category": "Java"
},
{
"command": "java.test.show.report",
"title": "%contributes.commands.java.test.show.report.title%",
"category": "Java"
},
{
"command": "java.test.explorer.refresh",
"title": "%contributes.commands.java.test.explorer.refresh.title%",
"icon": "$(refresh)",
"category": "Java"
},
{
"command": "java.test.config.migrate",
"title": "%contributes.commands.java.test.config.migrate.title%",
"command": "java.test.refreshExplorer",
"title": "%contributes.commands.java.test.refreshExplorer.title%",
"category": "Java"
}
],
"configuration": {
"title": "Java Test Runner",
"properties": {
"java.test.report.showAfterExecution": {
"type": "string",
"enum": [
"always",
"onFailure",
"never"
],
"default": "onFailure",
"description": "%configuration.java.test.report.showAfterExecution.description%",
"scope": "window"
},
"java.test.report.position": {
"type": "string",
"enum": [
"sideView",
"currentView"
],
"default": "sideView",
"description": "%configuration.java.test.report.position.description%",
"scope": "window"
},
"java.test.editor.enableShortcuts": {
"type": "boolean",
"default": true,
"description": "%configuration.java.test.editor.enableShortcuts.description%",
"scope": "application"
},
"java.test.log.level": {
"type": "string",
"enum": [
"error",
"info",
"verbose"
],
"default": "info",
"description": "%configuration.java.test.log.level.description%",
"scope": "application"
},
"java.test.message.hintForDeprecatedConfig": {
"type": "boolean",
"default": true,
"description": "%configuration.java.test.message.hintForDeprecatedConfig.description%",
"scope": "application"
},
"java.test.message.hintForSetingDefaultConfig": {
"type": "boolean",
"default": true,
"description": "%configuration.java.test.message.hintForSetingDefaultConfig.description%",
"scope": "application"
},
"java.test.defaultConfig": {
"type": "string",
"description": "%configuration.java.test.defaultConfig.description%",
@ -458,8 +263,10 @@
"test": "npm run compile && node ./dist/test/index.js",
"lint": "gulp lint",
"build-plugin": "gulp build-plugin",
"build-resources": "gulp build-resources",
"vscode:prepublish": "gulp build-resources && webpack --mode production"
"vscode:prepublish": "webpack --mode production",
"download-api": "vscode-dts dev -f",
"postdownload-api": "vscode-dts main -f",
"postinstall": "npm run download-api"
},
"extensionDependencies": [
"redhat.java",
@ -471,37 +278,27 @@
"@types/lodash": "^4.14.150",
"@types/mocha": "^2.2.48",
"@types/node": "^14.14.33",
"@types/pug": "^2.0.4",
"@types/sinon": "^9.0.11",
"@types/vscode": "1.56.0",
"bootstrap": "^4.6.0",
"copy-webpack-plugin": "^6.4.1",
"gulp": "^4.0.2",
"gulp-remote-src": "^0.4.4",
"gulp-sass": "^5.0.0",
"gulp-tslint": "^8.1.4",
"mocha": "^7.1.2",
"pug": "^3.0.1",
"pug-loader": "^2.4.0",
"sass": "^1.35.1",
"sinon": "^9.0.2",
"ts-loader": "^5.4.5",
"tslint": "^5.20.1",
"typescript": "^4.2.4",
"vscode-test": "^1.3.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.11"
"webpack-cli": "^3.3.11",
"vscode-dts": "^0.3.1"
},
"dependencies": {
"compare-versions": "^3.6.0",
"@types/lru-cache": "^5.1.0",
"fs-extra": "^7.0.1",
"get-port": "^4.2.0",
"iconv-lite": "^0.4.24",
"vscode-languageclient": "6.0.0-next.9",
"lodash": "^4.17.19",
"lru-cache": "^6.0.0",
"vscode-extension-telemetry-wrapper": "0.9.0",
"vscode-tas-client": "^0.1.22",
"winston": "^3.2.1",
"winston-transport": "^4.3.0"
"vscode-languageclient": "6.0.0-next.9",
"vscode-tas-client": "^0.1.22"
}
}

Просмотреть файл

@ -1,28 +1,10 @@
{
"description": "Run and debug JUnit or TestNG test cases",
"contributes.commands.java.test.show.output.title": "Show Test Output",
"contributes.commands.java.test.open.log.title": "Open Test Runner Log",
"contributes.commands.java.test.explorer.run.title": "Run",
"contributes.commands.java.test.explorer.debug.title": "Debug",
"contributes.commands.java.test.explorer.runAll.title": "Run All Tests",
"contributes.commands.java.test.explorer.debugAll.title": "Debug All Tests",
"contributes.commands.java.test.explorer.run.config.title": "Run With Configuration",
"contributes.commands.java.test.explorer.debug.config.title": "Debug With Configuration",
"contributes.commands.java.test.show.report.title": "Show Test Report",
"contributes.commands.java.test.editor.run.title": "Run Tests",
"contributes.commands.java.test.editor.debug.title": "Debug Tests",
"contributes.commands.java.test.runFromJavaProjectExplorer": "Run Tests",
"contributes.commands.java.test.debugFromJavaProjectExplorer": "Debug Tests",
"contributes.commands.java.test.relaunch.title": "Relaunch the Tests",
"contributes.commands.java.test.cancel.title": "Cancel Test Job",
"contributes.commands.java.test.explorer.refresh.title": "Refresh",
"contributes.commands.java.test.config.migrate.title": "Migrate Deprecated 'launch.test.json'",
"configuration.java.test.report.showAfterExecution.description": "Specify if the test report will automatically be shown after execution",
"configuration.java.test.report.position.description": "Specify where to show the test report",
"configuration.java.test.editor.enableShortcuts.description": "Specify whether to show the Code Lenses in editor or not",
"configuration.java.test.log.level.description": "Specify the level of the test logs",
"configuration.java.test.message.hintForDeprecatedConfig.description": "Specify whether the extension will show hint dialog when deprecated configuration file is used",
"configuration.java.test.message.hintForSetingDefaultConfig.description": "Specify whether the extension will show hint to set default test configuration",
"contributes.commands.java.test.runFromJavaProjectExplorer.title": "Run Tests",
"contributes.commands.java.test.debugFromJavaProjectExplorer.title": "Debug Tests",
"contributes.commands.java.test.refreshExplorer.title": "Refresh",
"configuration.java.test.defaultConfig.description": "Specify the name of the default test configuration",
"configuration.java.test.config.description": "Specify the configurations for running the tests",
"configuration.java.test.config.item.description": "Specify the configuration item for running the tests",

Просмотреть файл

@ -1,28 +1,10 @@
{
"description": "运行并调试 JUnit 或 TestNG 测试用例",
"contributes.commands.java.test.show.output.title": "显示测试输出",
"contributes.commands.java.test.open.log.title": "打开测试运行日志",
"contributes.commands.java.test.explorer.run.title": "运行",
"contributes.commands.java.test.explorer.debug.title": "调试",
"contributes.commands.java.test.explorer.runAll.title": "运行所有测试用例",
"contributes.commands.java.test.explorer.debugAll.title": "调试所有测试用例",
"contributes.commands.java.test.explorer.run.config.title": "根据配置文件运行",
"contributes.commands.java.test.explorer.debug.config.title": "根据配置文件调试",
"contributes.commands.java.test.show.report.title": "显示测试报告",
"contributes.commands.java.test.editor.run.title": "运行测试用例",
"contributes.commands.java.test.editor.debug.title": "调试测试用例",
"contributes.commands.java.test.relaunch.title": "重新执行测试任务",
"contributes.commands.java.test.cancel.title": "取消测试任务",
"contributes.commands.java.test.runFromJavaProjectExplorer": "运行测试",
"contributes.commands.java.test.debugFromJavaProjectExplorer": "调试测试",
"contributes.commands.java.test.explorer.refresh.title": "刷新",
"contributes.commands.java.test.config.migrate.title": "迁移已弃用的 'launch.test.json' 文件",
"configuration.java.test.report.showAfterExecution.description": "设定测试报告是否会在测试完成后自动显示",
"configuration.java.test.report.position.description": "设定测试报告的显示位置",
"configuration.java.test.editor.enableShortcuts.description": "设定是否在编辑器内显示 Code Lens 快捷方式",
"configuration.java.test.log.level.description": "设定日志级别",
"configuration.java.test.message.hintForDeprecatedConfig.description": "设定插件是否会对使用弃用的配置文件进行提示",
"configuration.java.test.message.hintForSetingDefaultConfig.description": "设定插件是否会对设置默认测试配置项进行提示",
"contributes.commands.java.test.runFromJavaProjectExplorer.title": "运行测试",
"contributes.commands.java.test.debugFromJavaProjectExplorer.title": "调试测试",
"contributes.commands.java.test.refreshExplorer.title": "刷新",
"configuration.java.test.defaultConfig.description": "设定默认测试配置项的名称",
"configuration.java.test.config.description": "设定运行测试的配置信息",
"configuration.java.test.config.item.description": "设定运行测试时所用的配置项",

Просмотреть файл

@ -1,3 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.8942 21.9031H5.39416C4.99633 21.9031 4.6148 21.745 4.3335 21.4637C4.05219 21.1824 3.89416 20.8009 3.89416 20.4031H20.3942C20.3942 20.8009 20.2361 21.1824 19.9548 21.4637C19.6735 21.745 19.292 21.9031 18.8942 21.9031ZM17.0192 8.40307V11.3401C17.0211 12.276 16.7888 13.1976 16.3434 14.0208C15.898 14.844 15.2537 15.5427 14.4692 16.0531L14.2937 16.1671L15.6062 17.9986L15.8552 17.8351C16.9089 17.1111 17.7704 16.1413 18.3652 15.0097C18.96 13.878 19.2703 12.6185 19.2692 11.3401V8.40307H17.0192ZM20.4692 5.40307H13.0772L11.5937 6.88657H20.4752C20.7309 6.87941 20.9855 6.9236 21.2239 7.01653C21.4623 7.10946 21.6796 7.24924 21.8631 7.42761C22.0465 7.60598 22.1923 7.81932 22.2919 8.05501C22.3914 8.2907 22.4427 8.54396 22.4427 8.79982C22.4427 9.05568 22.3914 9.30895 22.2919 9.54464C22.1923 9.78033 22.0465 9.99367 21.8631 10.172C21.6796 10.3504 21.4623 10.4902 21.2239 10.5831C20.9855 10.676 20.7309 10.7202 20.4752 10.7131H20.3942V12.2131H20.4752C21.3782 12.2131 22.2443 11.8543 22.8829 11.2158C23.5214 10.5772 23.8802 9.71114 23.8802 8.80807C23.8802 7.90501 23.5214 7.03894 22.8829 6.40038C22.2443 5.76181 21.3782 5.40307 20.4752 5.40307H20.4692ZM11.4257 17.3866C9.83169 17.3858 8.30277 16.7544 7.17271 15.6303C6.04266 14.5061 5.40327 12.9805 5.39416 11.3866V11.1841H4.99216L3.89416 10.0861V11.3866C3.90367 13.3782 4.70122 15.2851 6.1125 16.6905C7.52377 18.0959 9.43399 18.8854 11.4257 18.8866C12.3188 18.8769 13.2025 18.703 14.0327 18.3736L13.1327 17.1166C12.5802 17.2907 12.0049 17.3817 11.4257 17.3866ZM13.1132 1.12207L6.14416 8.09257L2.17516 4.12207L1.11316 5.18407L5.61316 9.68407H6.67516L14.1752 2.18407L13.1132 1.12207Z" fill="white"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Просмотреть файл

@ -1,3 +0,0 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18.8942 21.9031H5.39416C4.99633 21.9031 4.6148 21.745 4.3335 21.4637C4.05219 21.1824 3.89416 20.8009 3.89416 20.4031H20.3942C20.3942 20.8009 20.2361 21.1824 19.9548 21.4637C19.6735 21.745 19.292 21.9031 18.8942 21.9031ZM17.0192 8.40307V11.3401C17.0211 12.276 16.7888 13.1976 16.3434 14.0208C15.898 14.844 15.2537 15.5427 14.4692 16.0531L14.2937 16.1671L15.6062 17.9986L15.8552 17.8351C16.9089 17.1111 17.7704 16.1413 18.3652 15.0097C18.96 13.878 19.2703 12.6185 19.2692 11.3401V8.40307H17.0192ZM20.4692 5.40307H13.0772L11.5937 6.88657H20.4752C20.7309 6.87941 20.9855 6.9236 21.2239 7.01653C21.4623 7.10946 21.6796 7.24924 21.8631 7.42761C22.0465 7.60598 22.1923 7.81932 22.2919 8.05501C22.3914 8.2907 22.4427 8.54396 22.4427 8.79982C22.4427 9.05568 22.3914 9.30895 22.2919 9.54464C22.1923 9.78033 22.0465 9.99367 21.8631 10.172C21.6796 10.3504 21.4623 10.4902 21.2239 10.5831C20.9855 10.676 20.7309 10.7202 20.4752 10.7131H20.3942V12.2131H20.4752C21.3782 12.2131 22.2443 11.8543 22.8829 11.2158C23.5214 10.5772 23.8802 9.71114 23.8802 8.80807C23.8802 7.90501 23.5214 7.03894 22.8829 6.40038C22.2443 5.76181 21.3782 5.40307 20.4752 5.40307H20.4692ZM11.4257 17.3866C9.83169 17.3858 8.30277 16.7544 7.17271 15.6303C6.04266 14.5061 5.40327 12.9805 5.39416 11.3866V11.1841H4.99216L3.89416 10.0861V11.3866C3.90367 13.3782 4.70122 15.2851 6.1125 16.6905C7.52377 18.0959 9.43399 18.8854 11.4257 18.8866C12.3188 18.8769 13.2025 18.703 14.0327 18.3736L13.1327 17.1166C12.5802 17.2907 12.0049 17.3817 11.4257 17.3866ZM13.1132 1.12207L6.14416 8.09257L2.17516 4.12207L1.11316 5.18407L5.61316 9.68407H6.67516L14.1752 2.18407L13.1132 1.12207Z" fill="#656565"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Просмотреть файл

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<defs>
<style type="text/css"><![CDATA[
g {
transform-origin: 8px 8px;
animation: 1s linear infinite rotate;
}
@keyframes rotate {
from { transform: rotate(0); }
to { transform: rotate(1turn); }
}
]]></style>
</defs>
<g>
<path d="M8 0.75C8.17188 0.75 8.33333 0.783854 8.48438 0.851562C8.63542 0.914062 8.76823 1.0026 8.88281 1.11719C8.9974 1.23177 9.08594 1.36458 9.14844 1.51562C9.21615 1.66667 9.25 1.82812 9.25 2C9.25 2.17188 9.21615 2.33333 9.14844 2.48438C9.08594 2.63542 8.9974 2.76823 8.88281 2.88281C8.76823 2.9974 8.63542 3.08854 8.48438 3.15625C8.33333 3.21875 8.17188 3.25 8 3.25C7.82812 3.25 7.66667 3.21875 7.51562 3.15625C7.36458 3.08854 7.23177 2.9974 7.11719 2.88281C7.0026 2.76823 6.91146 2.63542 6.84375 2.48438C6.78125 2.33333 6.75 2.17188 6.75 2C6.75 1.82812 6.78125 1.66667 6.84375 1.51562C6.91146 1.36458 7.0026 1.23177 7.11719 1.11719C7.23177 1.0026 7.36458 0.914062 7.51562 0.851562C7.66667 0.783854 7.82812 0.75 8 0.75ZM2.63281 3.75781C2.63281 3.60156 2.66146 3.45573 2.71875 3.32031C2.77604 3.1849 2.85417 3.06771 2.95312 2.96875C3.05729 2.86458 3.17708 2.78385 3.3125 2.72656C3.45312 2.66406 3.60156 2.63281 3.75781 2.63281C3.91406 2.63281 4.0599 2.66406 4.19531 2.72656C4.33073 2.78385 4.44792 2.86458 4.54688 2.96875C4.65104 3.06771 4.73177 3.1849 4.78906 3.32031C4.85156 3.45573 4.88281 3.60156 4.88281 3.75781C4.88281 3.91406 4.85156 4.0625 4.78906 4.20312C4.73177 4.33854 4.65104 4.45833 4.54688 4.5625C4.44792 4.66146 4.33073 4.73958 4.19531 4.79688C4.0599 4.85417 3.91406 4.88281 3.75781 4.88281C3.60156 4.88281 3.45312 4.85417 3.3125 4.79688C3.17708 4.73958 3.05729 4.66146 2.95312 4.5625C2.85417 4.45833 2.77604 4.33854 2.71875 4.20312C2.66146 4.0625 2.63281 3.91406 2.63281 3.75781ZM2 7C2.14062 7 2.27083 7.02604 2.39062 7.07812C2.51042 7.13021 2.61458 7.20312 2.70312 7.29688C2.79688 7.38542 2.86979 7.48958 2.92188 7.60938C2.97396 7.72917 3 7.85938 3 8C3 8.14062 2.97396 8.27083 2.92188 8.39062C2.86979 8.51042 2.79688 8.61719 2.70312 8.71094C2.61458 8.79948 2.51042 8.86979 2.39062 8.92188C2.27083 8.97396 2.14062 9 2 9C1.85938 9 1.72917 8.97396 1.60938 8.92188C1.48958 8.86979 1.38281 8.79948 1.28906 8.71094C1.20052 8.61719 1.13021 8.51042 1.07812 8.39062C1.02604 8.27083 1 8.14062 1 8C1 7.85938 1.02604 7.72917 1.07812 7.60938C1.13021 7.48958 1.20052 7.38542 1.28906 7.29688C1.38281 7.20312 1.48958 7.13021 1.60938 7.07812C1.72917 7.02604 1.85938 7 2 7ZM2.88281 12.2422C2.88281 12.1224 2.90625 12.0104 2.95312 11.9062C3 11.7969 3.0625 11.7031 3.14062 11.625C3.21875 11.5469 3.3099 11.4844 3.41406 11.4375C3.52344 11.3906 3.63802 11.3672 3.75781 11.3672C3.8776 11.3672 3.98958 11.3906 4.09375 11.4375C4.20312 11.4844 4.29688 11.5469 4.375 11.625C4.45312 11.7031 4.51562 11.7969 4.5625 11.9062C4.60938 12.0104 4.63281 12.1224 4.63281 12.2422C4.63281 12.362 4.60938 12.4766 4.5625 12.5859C4.51562 12.6901 4.45312 12.7812 4.375 12.8594C4.29688 12.9375 4.20312 13 4.09375 13.0469C3.98958 13.0938 3.8776 13.1172 3.75781 13.1172C3.63802 13.1172 3.52344 13.0938 3.41406 13.0469C3.3099 13 3.21875 12.9375 3.14062 12.8594C3.0625 12.7812 3 12.6901 2.95312 12.5859C2.90625 12.4766 2.88281 12.362 2.88281 12.2422ZM8 13.25C8.20833 13.25 8.38542 13.3229 8.53125 13.4688C8.67708 13.6146 8.75 13.7917 8.75 14C8.75 14.2083 8.67708 14.3854 8.53125 14.5312C8.38542 14.6771 8.20833 14.75 8 14.75C7.79167 14.75 7.61458 14.6771 7.46875 14.5312C7.32292 14.3854 7.25 14.2083 7.25 14C7.25 13.7917 7.32292 13.6146 7.46875 13.4688C7.61458 13.3229 7.79167 13.25 8 13.25ZM11.6172 12.2422C11.6172 12.0651 11.6771 11.9167 11.7969 11.7969C11.9167 11.6771 12.0651 11.6172 12.2422 11.6172C12.4193 11.6172 12.5677 11.6771 12.6875 11.7969C12.8073 11.9167 12.8672 12.0651 12.8672 12.2422C12.8672 12.4193 12.8073 12.5677 12.6875 12.6875C12.5677 12.8073 12.4193 12.8672 12.2422 12.8672C12.0651 12.8672 11.9167 12.8073 11.7969 12.6875C11.6771 12.5677 11.6172 12.4193 11.6172 12.2422ZM14 7.5C14.1354 7.5 14.2526 7.54948 14.3516 7.64844C14.4505 7.7474 14.5 7.86458 14.5 8C14.5 8.13542 14.4505 8.2526 14.3516 8.35156C14.2526 8.45052 14.1354 8.5 14 8.5C13.8646 8.5 13.7474 8.45052 13.6484 8.35156C13.5495 8.2526 13.5 8.13542 13.5 8C13.5 7.86458 13.5495 7.7474 13.6484 7.64844C13.7474 7.54948 13.8646 7.5 14 7.5ZM12.2422 2.38281C12.4297 2.38281 12.6068 2.41927 12.7734 2.49219C12.9401 2.5651 13.0859 2.66406 13.2109 2.78906C13.3359 2.91406 13.4349 3.0599 13.5078 3.22656C13.5807 3.39323 13.6172 3.57031 13.6172 3.75781C13.6172 3.94531 13.5807 4.1224 13.5078 4.28906C13.4349 4.45573 13.3359 4.60156 13.2109 4.72656C13.0859 4.85156 12.9401 4.95052 12.7734 5.02344C12.6068 5.09635 12.4297 5.13281 12.2422 5.13281C12.0547 5.13281 11.8776 5.09635 11.7109 5.02344C11.5443 4.95052 11.3984 4.85156 11.2734 4.72656C11.1484 4.60156 11.0495 4.45573 10.9766 4.28906C10.9036 4.1224 10.8672 3.94531 10.8672 3.75781C10.8672 3.57031 10.9036 3.39323 10.9766 3.22656C11.0495 3.0599 11.1484 2.91406 11.2734 2.78906C11.3984 2.66406 11.5443 2.5651 11.7109 2.49219C11.8776 2.41927 12.0547 2.38281 12.2422 2.38281Z" fill="#C5C5C5"/>
</g>
</svg>

До

Ширина:  |  Высота:  |  Размер: 5.1 KiB

Просмотреть файл

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<defs>
<style type="text/css"><![CDATA[
g {
transform-origin: 8px 8px;
animation: 1s linear infinite rotate;
}
@keyframes rotate {
from { transform: rotate(0); }
to { transform: rotate(1turn); }
}
]]></style>
</defs>
<g>
<path d="M8 0.75C8.17188 0.75 8.33333 0.783854 8.48438 0.851562C8.63542 0.914062 8.76823 1.0026 8.88281 1.11719C8.9974 1.23177 9.08594 1.36458 9.14844 1.51562C9.21615 1.66667 9.25 1.82812 9.25 2C9.25 2.17188 9.21615 2.33333 9.14844 2.48438C9.08594 2.63542 8.9974 2.76823 8.88281 2.88281C8.76823 2.9974 8.63542 3.08854 8.48438 3.15625C8.33333 3.21875 8.17188 3.25 8 3.25C7.82812 3.25 7.66667 3.21875 7.51562 3.15625C7.36458 3.08854 7.23177 2.9974 7.11719 2.88281C7.0026 2.76823 6.91146 2.63542 6.84375 2.48438C6.78125 2.33333 6.75 2.17188 6.75 2C6.75 1.82812 6.78125 1.66667 6.84375 1.51562C6.91146 1.36458 7.0026 1.23177 7.11719 1.11719C7.23177 1.0026 7.36458 0.914062 7.51562 0.851562C7.66667 0.783854 7.82812 0.75 8 0.75ZM2.63281 3.75781C2.63281 3.60156 2.66146 3.45573 2.71875 3.32031C2.77604 3.1849 2.85417 3.06771 2.95312 2.96875C3.05729 2.86458 3.17708 2.78385 3.3125 2.72656C3.45312 2.66406 3.60156 2.63281 3.75781 2.63281C3.91406 2.63281 4.0599 2.66406 4.19531 2.72656C4.33073 2.78385 4.44792 2.86458 4.54688 2.96875C4.65104 3.06771 4.73177 3.1849 4.78906 3.32031C4.85156 3.45573 4.88281 3.60156 4.88281 3.75781C4.88281 3.91406 4.85156 4.0625 4.78906 4.20312C4.73177 4.33854 4.65104 4.45833 4.54688 4.5625C4.44792 4.66146 4.33073 4.73958 4.19531 4.79688C4.0599 4.85417 3.91406 4.88281 3.75781 4.88281C3.60156 4.88281 3.45312 4.85417 3.3125 4.79688C3.17708 4.73958 3.05729 4.66146 2.95312 4.5625C2.85417 4.45833 2.77604 4.33854 2.71875 4.20312C2.66146 4.0625 2.63281 3.91406 2.63281 3.75781ZM2 7C2.14062 7 2.27083 7.02604 2.39062 7.07812C2.51042 7.13021 2.61458 7.20312 2.70312 7.29688C2.79688 7.38542 2.86979 7.48958 2.92188 7.60938C2.97396 7.72917 3 7.85938 3 8C3 8.14062 2.97396 8.27083 2.92188 8.39062C2.86979 8.51042 2.79688 8.61719 2.70312 8.71094C2.61458 8.79948 2.51042 8.86979 2.39062 8.92188C2.27083 8.97396 2.14062 9 2 9C1.85938 9 1.72917 8.97396 1.60938 8.92188C1.48958 8.86979 1.38281 8.79948 1.28906 8.71094C1.20052 8.61719 1.13021 8.51042 1.07812 8.39062C1.02604 8.27083 1 8.14062 1 8C1 7.85938 1.02604 7.72917 1.07812 7.60938C1.13021 7.48958 1.20052 7.38542 1.28906 7.29688C1.38281 7.20312 1.48958 7.13021 1.60938 7.07812C1.72917 7.02604 1.85938 7 2 7ZM2.88281 12.2422C2.88281 12.1224 2.90625 12.0104 2.95312 11.9062C3 11.7969 3.0625 11.7031 3.14062 11.625C3.21875 11.5469 3.3099 11.4844 3.41406 11.4375C3.52344 11.3906 3.63802 11.3672 3.75781 11.3672C3.8776 11.3672 3.98958 11.3906 4.09375 11.4375C4.20312 11.4844 4.29688 11.5469 4.375 11.625C4.45312 11.7031 4.51562 11.7969 4.5625 11.9062C4.60938 12.0104 4.63281 12.1224 4.63281 12.2422C4.63281 12.362 4.60938 12.4766 4.5625 12.5859C4.51562 12.6901 4.45312 12.7812 4.375 12.8594C4.29688 12.9375 4.20312 13 4.09375 13.0469C3.98958 13.0938 3.8776 13.1172 3.75781 13.1172C3.63802 13.1172 3.52344 13.0938 3.41406 13.0469C3.3099 13 3.21875 12.9375 3.14062 12.8594C3.0625 12.7812 3 12.6901 2.95312 12.5859C2.90625 12.4766 2.88281 12.362 2.88281 12.2422ZM8 13.25C8.20833 13.25 8.38542 13.3229 8.53125 13.4688C8.67708 13.6146 8.75 13.7917 8.75 14C8.75 14.2083 8.67708 14.3854 8.53125 14.5312C8.38542 14.6771 8.20833 14.75 8 14.75C7.79167 14.75 7.61458 14.6771 7.46875 14.5312C7.32292 14.3854 7.25 14.2083 7.25 14C7.25 13.7917 7.32292 13.6146 7.46875 13.4688C7.61458 13.3229 7.79167 13.25 8 13.25ZM11.6172 12.2422C11.6172 12.0651 11.6771 11.9167 11.7969 11.7969C11.9167 11.6771 12.0651 11.6172 12.2422 11.6172C12.4193 11.6172 12.5677 11.6771 12.6875 11.7969C12.8073 11.9167 12.8672 12.0651 12.8672 12.2422C12.8672 12.4193 12.8073 12.5677 12.6875 12.6875C12.5677 12.8073 12.4193 12.8672 12.2422 12.8672C12.0651 12.8672 11.9167 12.8073 11.7969 12.6875C11.6771 12.5677 11.6172 12.4193 11.6172 12.2422ZM14 7.5C14.1354 7.5 14.2526 7.54948 14.3516 7.64844C14.4505 7.7474 14.5 7.86458 14.5 8C14.5 8.13542 14.4505 8.2526 14.3516 8.35156C14.2526 8.45052 14.1354 8.5 14 8.5C13.8646 8.5 13.7474 8.45052 13.6484 8.35156C13.5495 8.2526 13.5 8.13542 13.5 8C13.5 7.86458 13.5495 7.7474 13.6484 7.64844C13.7474 7.54948 13.8646 7.5 14 7.5ZM12.2422 2.38281C12.4297 2.38281 12.6068 2.41927 12.7734 2.49219C12.9401 2.5651 13.0859 2.66406 13.2109 2.78906C13.3359 2.91406 13.4349 3.0599 13.5078 3.22656C13.5807 3.39323 13.6172 3.57031 13.6172 3.75781C13.6172 3.94531 13.5807 4.1224 13.5078 4.28906C13.4349 4.45573 13.3359 4.60156 13.2109 4.72656C13.0859 4.85156 12.9401 4.95052 12.7734 5.02344C12.6068 5.09635 12.4297 5.13281 12.2422 5.13281C12.0547 5.13281 11.8776 5.09635 11.7109 5.02344C11.5443 4.95052 11.3984 4.85156 11.2734 4.72656C11.1484 4.60156 11.0495 4.45573 10.9766 4.28906C10.9036 4.1224 10.8672 3.94531 10.8672 3.75781C10.8672 3.57031 10.9036 3.39323 10.9766 3.22656C11.0495 3.0599 11.1484 2.91406 11.2734 2.78906C11.3984 2.66406 11.5443 2.5651 11.7109 2.49219C11.8776 2.41927 12.0547 2.38281 12.2422 2.38281Z" fill="#424242"/>
</g>
</svg>

До

Ширина:  |  Высота:  |  Размер: 5.1 KiB

Просмотреть файл

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.7 13.7L5 13L9.6 8.4L5 3.7L5.7 3L10.7 8V8.7L5.7 13.7Z" fill="var(--vscode-foreground)"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 202 B

Просмотреть файл

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.75 8C12.75 10.4853 10.7353 12.5 8.24999 12.5C6.41795 12.5 4.84162 11.4052 4.13953 9.83416L2.74882 10.399C3.67446 12.5186 5.78923 14 8.24999 14C11.5637 14 14.25 11.3137 14.25 8C14.25 4.68629 11.5637 2 8.24999 2C6.3169 2 4.59732 2.91418 3.5 4.3338V2.5H2V6.5L2.75 7.25H6.25V5.75H4.35201C5.13008 4.40495 6.58436 3.5 8.24999 3.5C10.7353 3.5 12.75 5.51472 12.75 8Z" fill="var(--vscode-debugIcon-restartForeground)"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 566 B

Просмотреть файл

@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.06065 3.85356L5.91421 6L5.2071 5.29289L6.49999 4H3.5C3.10218 4 2.72064 4.15804 2.43934 4.43934C2.15804 4.72065 2 5.10218 2 5.5C2 5.89783 2.15804 6.27936 2.43934 6.56066C2.72064 6.84197 3.10218 7 3.5 7H4V8H3.5C2.83696 8 2.20107 7.73661 1.73223 7.26777C1.26339 6.79893 1 6.16305 1 5.5C1 4.83696 1.26339 4.20108 1.73223 3.73224C2.20107 3.2634 2.83696 3 3.5 3H6.49999L6.49999 3H6.49996L6 2.50004V2.50001L5.2071 1.70711L5.91421 1L8.06065 3.14645L8.06065 3.85356ZM5 6.50003L5.91421 7.41424L6 7.32845V14H14V7H10V3H9.06065V2.73227L8.32838 2H11.2L11.5 2.1L14.9 5.6L15 6V14.5L14.5 15H5.5L5 14.5V9.00003V6.50003ZM11 3V6H13.9032L11 3Z" fill="var(--vscode-textLink-foreground)"/>
</svg>

До

Ширина:  |  Высота:  |  Размер: 821 B

Просмотреть файл

@ -1,97 +0,0 @@
include /report_method_table.pug
doctype html
html(lang="en")
head
meta(http-equiv="Content-Security-Policy" content=`default-src 'none'; style-src 'nonce-${nonce}'; script-src 'nonce-${nonce}'; img-src ${cspSource}`)
meta(charset="utf-8")
meta(name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no")
style(nonce=`${nonce}`)
include /css/report.css
script(nonce=`${nonce}`, src=`${resourceBaseUri}/js/jquery-3.5.1.slim.min.js`, integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj", crossorigin="anonymous")
script(nonce=`${nonce}`, src=`${resourceBaseUri}/js/bootstrap.bundle.min.js`, integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns", crossorigin="anonymous")
script(nonce=`${nonce}`).
$(() => {
const vscode = acquireVsCodeApi();
$('.list-group-item a[title="Navigate to Source"]').click((e) => {
const $this = $(e.currentTarget);
vscode.postMessage({
command: 'java.test.open.document',
uri: $this.attr('uri'),
range: $this.attr('range'),
fullName: $this.attr('fullName'),
});
return false;
});
$('code.word-break-all a').click((e) => {
const $this = $(e.currentTarget);
vscode.postMessage({
command: 'java.test.report.openStackTrace',
fullName: $this.attr('fullName'),
trace: $this.attr('trace'),
});
return false;
});
$('#relaunch-tab').click((e) => {
vscode.postMessage({
command: 'java.test.relaunch',
});
return false;
});
//- rotate the arrow icon in accordion
$(".collapse").on('show.bs.collapse', function(){
$(this).parent().find("svg").first().addClass("rotate");
}).on('hide.bs.collapse', function(){
$(this).parent().find("svg").first().removeClass("rotate");
});
$(document).keydown(function(event) {
if (event.which === 27) {
let detailCollapse = $(':focus').closest("div.collapse[id$='detail']");
if (detailCollapse.length === 0) {
$('div.collapse').collapse('hide');
} else {
detailCollapse.collapse('hide');
detailCollapse.closest("li.list-group-item-action").find("a.method-link").focus();
}
}
});
});
body.container-fluid
ul.nav.nav-pills.mt-3(role='tablist')
li.nav-item.d-inline-flex
button.btn.btn-sm.btn-info.active.mr-2.mb-2(id='all-tab', data-toggle='pill', href='#all', role='tab', aria-controls='all') All
span.badge.badge-light.ml-2 #{allCount}
if failedCount
li.nav-item.d-inline-flex
button.btn.btn-sm.btn-danger.mr-2.mb-2(id='fail-tab', data-toggle='pill', href='#fail', role='tab', aria-controls='fail') Failed
span.badge.badge-light.ml-2 #{failedCount}
if passedCount
li.nav-item.d-inline-flex
button.btn.btn-sm.btn-success.mr-2.mb-2(id='pass-tab', data-toggle='pill', href='#pass', role='tab', aria-controls='pass') Passed
span.badge.badge-light.ml-2 #{passedCount}
if skippedCount
li.nav-item.d-inline-flex
button.btn.btn-sm.btn-secondary.mr-2.mb-2(id='skip-tab', data-toggle='pill', href='#skip', role='tab', aria-controls='skip') Skipped
span.badge.badge-light.ml-2 #{skippedCount}
li.nav-item.d-inline-flex
button.btn.btn-sm.btn-dark.mb-2(id='relaunch-tab', data-toggle='tooltip' data-placement='bottom' title='Relaunch the tests')
include /images/debug-restart.svg
span(aria-label="The below part contains collapsible components to show the stacktrace of failed methods. If you want to collapse the expanded view, you can press ESC.", tabindex="0")
div.tab-content
div.tab-pane.fade.show.active(id='all', role='tabpanel', aria-labelledby='all-tab')
+collapseMethodTable(tests, 'detail-all')
if failedCount
div.tab-pane.fade(id='fail', role='tabpanel', aria-labelledby='fail-tab')
+collapseMethodTable(failedTests, 'detail-fail')
if passedCount
div.tab-pane.fade(id='pass', role='tabpanel', aria-labelledby='pass-tab')
+collapseMethodTable(passedTests, 'detail-pass')
if skippedCount
div.tab-pane.fade(id='skip', role='tabpanel', aria-labelledby='skip-tab')
+collapseMethodTable(skippedTests, 'detail-skip')

Просмотреть файл

@ -1,65 +0,0 @@
mixin collapseMethodTable(children, type)
- var regex = /(\s?at\s+)([\w$\\.]+\/)?((?:[\w$]+\.)+[<\w$>]+)\(([\w-$]+\.java:\d+)\)/
- var classIdx = 0
//- See https://github.com/pugjs/pug/issues/2559#issuecomment-289873794 for iterating a map in Pug
each entry in [...children]
- classIdx++
ul.list-group.mb-3
li.list-group-item.active(tabindex="0", aria-label=`Class: ${entry[0]}`)
h5.mb-0.text-truncate-left #{entry[0]}
- var methodIdx = 0
each method in entry[1]
- methodIdx++
li.list-group-item.list-group-item-action
div.row.accordion
a.col-7.method-link.collapsed.px-1(id=`${type}-${classIdx}-${methodIdx}-title`, href="", role="button", data-toggle="collapse", data-target=`#${type}-${classIdx}-${methodIdx}-detail`, aria-expanded="false", aria-controls=`${type}-${classIdx}-${methodIdx}-detail`, aria-label=`${method.status} method: ${method.displayName}.`)
div.text-truncate
include /images/chevron-right.svg
span.ml-1 #{method.displayName}
div.col-2.text-right.p-0
if !method.status
span.badge.badge-warning Not run
else if method.status === 'Pass'
span.badge.badge-success Passed
else if method.status === 'Fail'
span.badge.badge-danger Failed
else
span.badge.badge-secondary Skipped
div.col-2.text-right.p-0 #{method.duration >= 0 ? parseFloat((method.duration/1000).toFixed(2)) + "s" : "N/A"}
div.col-1.text-right.px-1
a(href="#", title="Navigate to Source", uri=`${method.location && method.location.uri ? method.location.uri : ''}`, range=`${method.location && method.location.range ? JSON.stringify(method.location.range) : ''}`, fullname=`${method.fullName}`)
include /images/go-to-file.svg
div.mt-2.pl-2(id=`${type}-${classIdx}-${methodIdx}-detail`, class="collapse", aria-labelledby=`${type}-${classIdx}-${methodIdx}-title`, tabindex="0")
div.row
div.col
h6 Message:
div.row
div.col.mb-1 #{method.message ? method.message : "N/A"}
div.row
div.col
h6 Stack trace:
div.row
div.col
if method.trace
pre.pre-wrap
- var traces = method.trace.split(/^Caused by:/gm)
code.word-break-all
each val, idx in traces
if idx > 0
br
span.text-warning Caused by:
if canResolveStackTrace
- var lines = traces[idx].split(/\r?\n/g);
each line in lines
- var result = regex.exec(line);
if result && result.length === 5
span #{result[1] + (result[2] || "") + result[3]}(
a(href="#" fullName=`${method.fullName}` trace=`${line}`) #{result[4]}
| )
else
span #{line}
br
else
span #{traces[idx]}
else
span N/A

Просмотреть файл

@ -1,45 +0,0 @@
$body-bg: var(--vscode-editor-background);
$body-color: var(--vscode-foreground);
$link-color: var(--vscode-textLink-foreground);
$link-hover-color: $link-color;
$link-hover-decoration: none !default;
$list-group-bg: var(--vscode-notifications-background);
$list-group-active-bg: var(--vscode-titleBar-activeBackground);
$list-group-border-width: 0;
$list-group-action-active-bg: $list-group-bg;
$list-group-active-color: var(--vscode-foreground);
$list-group-action-color: var(--vscode-foreground);
$list-group-hover-bg: var(--vscode-list-hoverBackground);
$font-size-base: .85rem !default;
$pre-color: var(--vscode-foreground);
$code-color: var(--vscode-foreground);
@import "../../../node_modules/bootstrap/scss/bootstrap";
.text-truncate-left {
@extend .text-truncate;
@extend .text-left;
direction: rtl;
}
code.word-break-all {
word-break: break-all;
}
code.text-warning {
color: var(--vscode-editorWarning-foreground);
}
pre.pre-wrap {
white-space: pre-wrap;
}
.rotate {
transform: rotate(90deg);
}
.method-link, .method-link:hover {
color: var(--vscode-foreground);
}

Просмотреть файл

@ -1,60 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ConfigurationChangeEvent, Disposable, DocumentSelector, languages, RelativePattern, workspace } from 'vscode';
import { testSourceProvider } from '../../extension.bundle';
import { ENABLE_EDITOR_SHORTCUTS_KEY } from '../constants/configs';
import { parseDocumentSelector } from '../utils/uiUtils';
import { TestCodeLensProvider } from './TestCodeLensProvider';
class TestCodeLensController implements Disposable {
private internalProvider: TestCodeLensProvider;
private registeredProvider: Disposable | undefined;
private configurationChangeListener: Disposable;
constructor() {
this.internalProvider = new TestCodeLensProvider();
this.configurationChangeListener = workspace.onDidChangeConfiguration((event: ConfigurationChangeEvent) => {
if (event.affectsConfiguration(ENABLE_EDITOR_SHORTCUTS_KEY)) {
this.setCodeLensVisibility();
}
}, this);
this.setCodeLensVisibility();
}
public async registerCodeLensProvider(): Promise<void> {
if (this.registeredProvider) {
this.registeredProvider.dispose();
}
const patterns: RelativePattern[] = await testSourceProvider.getTestSourcePattern();
const documentSelector: DocumentSelector = parseDocumentSelector(patterns);
this.registeredProvider = languages.registerCodeLensProvider(documentSelector, this.internalProvider);
}
public refresh(): void {
this.internalProvider.refresh();
}
public dispose(): void {
this.internalProvider.dispose();
if (this.registeredProvider) {
this.registeredProvider.dispose();
}
this.configurationChangeListener.dispose();
}
private setCodeLensVisibility(): void {
this.internalProvider.setIsActivated(this.isCodeLensEnabled());
}
private isCodeLensEnabled(): boolean {
return workspace.getConfiguration().get<boolean>(ENABLE_EDITOR_SHORTCUTS_KEY, true);
}
}
export const testCodeLensController: TestCodeLensController = new TestCodeLensController();

Просмотреть файл

@ -1,152 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationToken, CodeLens, CodeLensProvider, Disposable, Event, EventEmitter, TextDocument } from 'vscode';
import { JavaTestRunnerCommands } from '../constants/commands';
import { logger } from '../logger/logger';
import { ITestItem, TestLevel } from '../protocols';
import { ITestResult, TestStatus } from '../runners/models';
import { testItemModel } from '../testItemModel';
import { testResultManager } from '../testResultManager';
export class TestCodeLensProvider implements CodeLensProvider, Disposable {
private onDidChangeCodeLensesEmitter: EventEmitter<void> = new EventEmitter<void>();
private isActivated: boolean = true;
get onDidChangeCodeLenses(): Event<void> {
return this.onDidChangeCodeLensesEmitter.event;
}
public setIsActivated(isActivated: boolean): void {
this.isActivated = isActivated;
this.refresh();
}
public refresh(): void {
this.onDidChangeCodeLensesEmitter.fire();
}
public async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
if (!this.isActivated) {
return [];
}
try {
const items: ITestItem[] = await testItemModel.getItemsForCodeLens(document.uri, token);
return this.getCodeLenses(items);
} catch (error) {
logger.error('Failed to provide Code Lens', error);
return [];
}
}
public dispose(): void {
this.onDidChangeCodeLensesEmitter.dispose();
}
private getCodeLenses(items: ITestItem[]): CodeLens[] {
const codeLenses: CodeLens[] = [];
for (const item of items) {
codeLenses.push(
new CodeLens(
item.location.range,
{
title: 'Run Test',
command: JavaTestRunnerCommands.RUN_TEST_FROM_CODELENS,
tooltip: 'Run Test',
arguments: [item],
},
),
new CodeLens(
item.location.range,
{
title: 'Debug Test',
command: JavaTestRunnerCommands.DEBUG_TEST_FROM_CODELENS,
tooltip: 'Debug Test',
arguments: [item],
},
),
);
const resultCodeLens: CodeLens | undefined = this.getResultCodeLens(item);
if (resultCodeLens) {
codeLenses.push(resultCodeLens);
}
}
return codeLenses;
}
private getResultCodeLens(item: ITestItem): CodeLens | undefined {
if (item.level === TestLevel.Method) {
const result: ITestResult | undefined = testResultManager.getResultById(item.id);
if (result && result.status) {
return new CodeLens(
item.location.range,
{
title: this.getResultIcon(result),
command: JavaTestRunnerCommands.SHOW_TEST_REPORT,
tooltip: 'Show Test Report',
arguments: [[result]],
},
);
}
} else if (item.level === TestLevel.Class) {
const childResults: Array<ITestResult | undefined> = [];
this.getAllMethodResults(childResults, item);
const title: string = this.getResultIcons(childResults);
if (title) {
return new CodeLens(
item.location.range,
{
title,
command: JavaTestRunnerCommands.SHOW_TEST_REPORT,
tooltip: 'Show Test Report',
arguments: [childResults],
},
);
}
}
return undefined;
}
private getAllMethodResults(childResults: Array<ITestResult | undefined>, item: ITestItem): void {
if (!item.children) {
return undefined;
}
for (const childId of item.children) {
const child: ITestItem | undefined = testItemModel.getItemById(childId);
if (!child) {
continue;
}
if (child.level === TestLevel.Class) {
this.getAllMethodResults(childResults, child);
} else if (child.level === TestLevel.Method) {
childResults.push(testResultManager.getResultById(childId));
}
}
}
private getResultIcon(result: ITestResult): string {
switch (result.status) {
case TestStatus.Pass:
return '$(check)';
case TestStatus.Fail:
return '$(x)';
default:
return '';
}
}
private getResultIcons(results: Array<ITestResult | undefined>): string {
const passNum: number = results.filter((result: ITestResult | undefined) => result && result.status === TestStatus.Pass).length;
const failNum: number = results.filter((result: ITestResult | undefined) => result && result.status === TestStatus.Fail).length;
if (failNum > 0) {
return '$(x)';
} else if (passNum === 0) {
return '';
} else if (passNum === results.length) {
return '$(check)';
}
return '?';
}
}

Просмотреть файл

@ -1,114 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationToken, DebugConfiguration, Progress, ProgressLocation, Range, TextDocument, Uri, ViewColumn, window, workspace } from 'vscode';
import { IProgressReporter } from '../debugger.api';
import { progressProvider } from '../extension';
import { logger } from '../logger/logger';
import { ITestItem, TestKind, TestLevel } from '../protocols';
import { IRunnerContext } from '../runners/models';
import { runnerScheduler } from '../runners/runnerScheduler';
import { testItemModel } from '../testItemModel';
import { executeTestsFromUri } from './runFromUri';
export async function openTextDocument(uri: Uri, range?: Range): Promise<void> {
const document: TextDocument = await workspace.openTextDocument(uri);
await window.showTextDocument(document, {selection: range, viewColumn: ViewColumn.One});
}
export async function runTestsFromExplorer(node?: ITestItem, launchConfiguration?: DebugConfiguration): Promise<void> {
return executeTestsFromExplorer(false /* isDebug */, node, launchConfiguration);
}
export async function debugTestsFromExplorer(node?: ITestItem, launchConfiguration?: DebugConfiguration): Promise<void> {
return executeTestsFromExplorer(true /* isDebug */, node, launchConfiguration);
}
async function executeTestsFromExplorer(isDebug: boolean, node?: ITestItem, launchConfiguration?: DebugConfiguration): Promise<void> {
const runnerContext: IRunnerContext = {
scope: TestLevel.Root,
testUri: '',
fullName: '',
projectName: '',
kind: TestKind.None,
isDebug,
tests: [],
};
if (node) {
runnerContext.scope = node.level;
runnerContext.projectName = node.project;
runnerContext.testUri = Uri.parse(node.location.uri).toString();
if (node.level >= TestLevel.Package) {
runnerContext.fullName = node.fullName;
}
if (node.level === TestLevel.Method) {
runnerContext.tests = [node];
}
}
return executeTests(runnerContext, launchConfiguration);
}
export async function runTestsFromJavaProjectExplorer(node: any, isDebug: boolean): Promise<void> {
if (node._nodeData.kind === 6 /*PrimaryType*/) {
return executeTestsFromUri(Uri.parse(node._nodeData.uri), undefined, isDebug);
}
const runnerContext: IRunnerContext = {
scope: TestLevel.Package,
testUri: node._nodeData.uri,
fullName: node._nodeData.name,
projectName: node._project.name,
kind: TestKind.None,
isDebug,
tests: [],
// isPackage is only available when the explorer is in hierarchical mode
isHierarchicalPackage: node._nodeData.isPackage,
};
return executeTests(runnerContext);
}
async function executeTests(runnerContext: IRunnerContext, launchConfiguration?: DebugConfiguration): Promise<void> {
const progressReporter: IProgressReporter | undefined = progressProvider?.createProgressReporter(runnerContext.isDebug ? 'Debug Test' : 'Run Test', ProgressLocation.Notification, true);
if (runnerContext.tests.length === 0) {
try {
await searchTestItems(runnerContext, progressReporter);
} catch (e) {
// so far the promise is only rejected on cancellation
logger.info('Test job is canceled.\n');
progressReporter?.done();
return;
}
}
if (runnerContext.tests.length === 0) {
window.showInformationMessage(`No tests found under: '${Uri.parse(runnerContext.testUri).fsPath}'.`);
progressReporter?.done();
return;
}
return runnerScheduler.run(runnerContext, progressReporter, launchConfiguration);
}
async function searchTestItems(runnerContext: IRunnerContext, progressReporter?: IProgressReporter): Promise<void> {
return new Promise<void>(async (resolve: () => void, reject: () => void): Promise<void> => {
const searchImpl: (token: CancellationToken) => Promise<void> = async (token: CancellationToken) => {
token.onCancellationRequested(reject);
runnerContext.tests = await testItemModel.getAllNodes(runnerContext.scope, runnerContext.fullName, runnerContext.testUri, runnerContext.isHierarchicalPackage, token);
return resolve();
};
if (progressReporter) {
progressReporter.report('Searching tests...');
return searchImpl(progressReporter.getCancellationToken());
} else {
window.withProgress(
{ location: ProgressLocation.Notification, cancellable: true },
async (progress: Progress<any>, token: CancellationToken): Promise<void> => {
progress.report({ message: 'Searching tests...' });
return searchImpl(token);
},
);
}
});
}

Просмотреть файл

@ -3,11 +3,12 @@
import { commands, Disposable, ExtensionContext, QuickInputButton, QuickPick, QuickPickItem, TextEdit, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
import * as protocolConverter from 'vscode-languageclient/lib/protocolConverter';
import * as commandUtils from '../utils/commandUtils';
import { JavaTestRunnerDelegateCommands } from '../constants';
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
const converter: protocolConverter.Converter = protocolConverter.createConverter();
export async function generateTests(uri: Uri, cursorOffset: number): Promise<void> {
const edit: WorkspaceEdit = converter.asWorkspaceEdit(await commandUtils.generateTests(uri, cursorOffset));
const edit: WorkspaceEdit = converter.asWorkspaceEdit(await askServerToGenerateTests(uri, cursorOffset));
if (edit) {
await workspace.applyEdit(edit);
const entries: Array<[Uri, TextEdit[]]> = edit.entries();
@ -115,6 +116,10 @@ export async function registerAskForInputCommand(context: ExtensionContext): Pro
}));
}
async function askServerToGenerateTests(uri: Uri, cursorOffset: number): Promise<any> {
return await executeJavaLanguageServerCommand<any>(JavaTestRunnerDelegateCommands.GENERATE_TESTS, uri.toString(), cursorOffset);
}
function checkJavaQualifiedName(value: string): string {
if (!value || !value.trim()) {
return 'Input cannot be empty.';

Просмотреть файл

@ -1,25 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fse from 'fs-extra';
import * as path from 'path';
import { TextDocument, ViewColumn, window, workspace } from 'vscode';
import { LOG_FILE_NAME } from '../constants/configs';
import { logger } from '../logger/logger';
import { outputChannelTransport } from '../logger/outputChannelTransport';
export async function openLogFile(storagePath: string): Promise<void> {
const logFilePath: string = path.join(storagePath, LOG_FILE_NAME);
if (!await fse.pathExists(logFilePath)) {
const errorMsg: string = 'The log file does not exist.';
logger.error(errorMsg);
await window.showErrorMessage(errorMsg);
return;
}
const textDocument: TextDocument = await workspace.openTextDocument(logFilePath);
window.showTextDocument(textDocument, ViewColumn.Active, false);
}
export function showOutputChannel(): void {
outputChannelTransport.show();
}

Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import { TestItem, TestRunRequest, Uri } from 'vscode';
import { sendError } from 'vscode-extension-telemetry-wrapper';
import { loadChildren, runTests, testController } from '../controller/testController';
import { loadJavaProjects, updateItemForDocument } from '../controller/utils';
import { IProgressReporter } from '../debugger.api';
import { progressProvider } from '../extension';
import { TestLevel } from '../types';
export async function runTestsFromJavaProjectExplorer(node: any, isDebug: boolean): Promise<void> {
const testLevel: TestLevel = getTestLevel(node._nodeData);
const isHierarchicalMode: boolean = isHierarchical(node._nodeData);
const progressReporter: IProgressReporter | undefined = progressProvider?.createProgressReporter(isDebug ? 'Debug Test' : 'Run Test');
progressReporter?.report('Searching tests...');
const tests: TestItem[] = [];
if (testLevel === TestLevel.Class) {
tests.push(...await updateItemForDocument(node._nodeData.uri));
} else if (testLevel === TestLevel.Package) {
if (!testController?.items.size) {
await loadJavaProjects();
}
const projectName: string = node._project.name;
const projectItem: TestItem | undefined = testController!.items.get(projectName);
if (!projectItem) {
sendError(new Error('The project name of the node in java project explorer cannot be found in test explorer'));
return;
}
await loadChildren(projectItem);
const nodeFsPath: string = Uri.parse(node._nodeData.uri).fsPath;
projectItem.children.forEach((child: TestItem) => {
const itemPath: string = child.uri?.fsPath || '';
if (isHierarchicalMode || node._nodeData.kind === 4 /*packageRoot*/) {
// if the selected node is a package root or the view is in hierarchical mode,
// all the test items whose path start from the path of the selected node will be added
if (itemPath.startsWith(nodeFsPath)) {
tests.push(child);
}
} else {
// in flat mode, we require the paths exact match
if (path.relative(itemPath, nodeFsPath) === '') {
tests.push(child);
}
}
});
}
const request: TestRunRequest = new TestRunRequest(tests, undefined);
await runTests(request, { progressReporter, isDebug });
}
function getTestLevel(nodeData: any): TestLevel {
// The command will only register on the class/package/packageRoot
// nodes of the Java Project explorer
if (nodeData.kind === 6 /*PrimaryType*/) {
return TestLevel.Class;
} else {
return TestLevel.Package;
}
}
function isHierarchical(nodeData: any): boolean {
return !!nodeData.isPackage;
}

Просмотреть файл

@ -1,20 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ITestItem } from '../protocols';
import { IRunnerContext } from '../runners/models';
import { runnerScheduler } from '../runners/runnerScheduler';
export async function runFromCodeLens(test: ITestItem, isDebug: boolean): Promise<void> {
const runnerContext: IRunnerContext = {
scope: test.level,
testUri: test.location.uri,
fullName: test.fullName,
kind: test.kind,
projectName: test.project,
tests: [test],
isDebug,
};
await runnerScheduler.run(runnerContext);
}

Просмотреть файл

@ -1,56 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import { Uri, window, workspace } from 'vscode';
import { IProgressReporter } from '../debugger.api';
import { progressProvider } from '../extension';
import { ITestItem, TestLevel } from '../protocols';
import { IRunnerContext } from '../runners/models';
import { runnerScheduler } from '../runners/runnerScheduler';
import { testItemModel } from '../testItemModel';
export async function executeTestsFromUri(uri: Uri | undefined, progressReporter: IProgressReporter | undefined, isDebug: boolean): Promise<void> {
if (!uri) {
if (!window.activeTextEditor) {
return;
}
uri = window.activeTextEditor.document.uri;
}
if (uri.scheme !== 'file' || path.extname(uri.fsPath) !== '.java') {
return;
}
if (!workspace.getWorkspaceFolder(uri)) {
window.showInformationMessage(`The file: '${uri.fsPath}' does not belong to the current workspace.`);
return;
}
progressReporter = progressReporter || progressProvider?.createProgressReporter(isDebug ? 'Debug Test' : 'Run Test');
progressReporter?.report('Searching tests...');
const tests: ITestItem[] = await testItemModel.getItemsForCodeLens(uri);
const testItemForPrimaryType: ITestItem | undefined = tests.find((test: ITestItem) => {
return test.level === TestLevel.Class;
});
if (!testItemForPrimaryType) {
window.showInformationMessage(`No tests in file: '${uri.fsPath}'.`);
progressReporter?.done();
return;
}
const runnerContext: IRunnerContext = {
scope: testItemForPrimaryType.level,
testUri: testItemForPrimaryType.location.uri,
fullName: testItemForPrimaryType.fullName,
kind: testItemForPrimaryType.kind,
projectName: testItemForPrimaryType.project,
tests: [testItemForPrimaryType],
isDebug,
};
await runnerScheduler.run(runnerContext, progressReporter);
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { DebugConfiguration, TestItem, TestRunRequest } from 'vscode';
import { runTests, testController } from '../controller/testController';
import { loadJavaProjects } from '../controller/utils';
/**
* This function is used to exposed as a command, which other extensions can trigger
* their customized run/debug requests to test runner. (e.g. the PDE extension)
*
* Note that, the test item in the method parameters is not the exact test item in the test explorer (another instance).
* To get the metadata of the real test item, we have to record the path from test item to root, and then trace back that
* path to get the real one.
*/
export async function runTestsFromTestExplorer(testItem: TestItem, launchConfiguration: DebugConfiguration, isDebug: boolean): Promise<void> {
const pathToRoot: string[] = [];
do {
pathToRoot.push(testItem.id);
testItem = testItem.parent!;
} while (testItem.parent);
let currentItem: TestItem | undefined = testController?.items.get(pathToRoot.pop()!);
if (!currentItem) {
return;
}
while (pathToRoot.length) {
const id: string = pathToRoot.pop()!;
currentItem = currentItem.children.get(id);
if (!currentItem) {
return;
}
}
const request: TestRunRequest = new TestRunRequest([currentItem], undefined);
await runTests(request, { launchConfiguration, isDebug });
}
export async function refresh(): Promise<void> {
testController?.items.forEach((root: TestItem) => {
testController?.items.delete(root.id);
});
await loadJavaProjects();
}

Просмотреть файл

@ -1,11 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { commands, Position, QuickPickItem, Range, Uri, ViewColumn, window } from 'vscode';
import { ILocation } from '../../extension.bundle';
import { JavaTestRunnerCommands } from '../constants/commands';
import { logger } from '../logger/logger';
import { resolveStackTraceLocation, searchTestLocation } from '../utils/commandUtils';
import { commands, Position, Range, Uri, ViewColumn, window } from 'vscode';
import { JavaLanguageServerCommands } from '../constants';
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
export async function openStackTrace(trace: string, fullName: string): Promise<void> {
if (!trace || !fullName) {
@ -35,29 +33,7 @@ export async function openStackTrace(trace: string, fullName: string): Promise<v
}
}
export async function openTestSourceLocation(uri: string, range: string, fullName: string): Promise<void> {
if (uri && range) {
return commands.executeCommand(JavaTestRunnerCommands.OPEN_DOCUMENT, Uri.parse(uri), JSON.parse(range) as Range);
} else if (fullName) {
const methodEndIndex: number = fullName.indexOf('[');
const items: ILocation[] = await searchTestLocation(fullName.slice(fullName.indexOf('@') + 1, methodEndIndex < 0 ? undefined : methodEndIndex));
if (items.length === 1) {
return commands.executeCommand(JavaTestRunnerCommands.OPEN_DOCUMENT, Uri.parse(items[0].uri), items[0].range);
} else if (items.length > 1) {
const pick: ILocationQuickPick | undefined = await window.showQuickPick(items.map((item: ILocation) => {
return { label: fullName, detail: Uri.parse(item.uri).fsPath, location: item };
}), { placeHolder: 'Select the file you want to navigate to' });
if (pick) {
return commands.executeCommand(JavaTestRunnerCommands.OPEN_DOCUMENT, Uri.parse(pick.location.uri), pick.location.range);
}
} else {
logger.error('No test item could be found from Language Server.');
}
} else {
logger.error('Could not open the document, Neither the Uri nor full name is null.');
}
}
interface ILocationQuickPick extends QuickPickItem {
location: ILocation;
async function resolveStackTraceLocation(trace: string, projectNames: string[]): Promise<string> {
return await executeJavaLanguageServerCommand<string>(
JavaLanguageServerCommands.RESOLVE_STACKTRACE_LOCATION, trace, projectNames) || '';
}

67
src/constants.ts Normal file
Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export namespace JavaLanguageServerCommands {
export const EXECUTE_WORKSPACE_COMMAND: string = 'java.execute.workspaceCommand';
export const RESOLVE_STACKTRACE_LOCATION: string = 'java.project.resolveStackTraceLocation';
}
export namespace JavaTestRunnerDelegateCommands {
export const GET_TEST_SOURCE_PATH: string = 'vscode.java.test.get.testpath';
export const RESOLVE_JUNIT_ARGUMENT: string = 'vscode.java.test.junit.argument';
export const GENERATE_TESTS: string = 'vscode.java.test.generateTests';
export const FIND_JAVA_PROJECTS: string = 'vscode.java.test.findJavaProjects';
export const FIND_TEST_PACKAGES_AND_TYPES: string = 'vscode.java.test.findTestPackagesAndTypes';
export const FIND_DIRECT_CHILDREN_FOR_CLASS: string = 'vscode.java.test.findDirectTestChildrenForClass';
export const FIND_TEST_TYPES_AND_METHODS: string = 'vscode.java.test.findTestTypesAndMethods';
export const RESOLVE_PATH: string = 'vscode.java.test.resolvePath';
}
export namespace JavaTestRunnerCommands {
export const RUN_TEST_FROM_EDITOR: string = 'java.test.editor.run';
export const DEBUG_TEST_FROM_EDITOR: string = 'java.test.editor.debug';
export const RUN_TEST_FROM_JAVA_PROJECT_EXPLORER: string = 'java.test.runFromJavaProjectExplorer';
export const DEBUG_TEST_FROM_JAVA_PROJECT_EXPLORER: string = 'java.test.debugFromJavaProjectExplorer';
export const RUN_FROM_TEST_EXPLORER: string = 'java.test.explorer.run';
export const DEBUG_FROM_TEST_EXPLORER: string = 'java.test.explorer.debug';
export const REFRESH_TEST_EXPLORER: string = 'java.test.refreshExplorer';
export const JAVA_TEST_GENERATE_TESTS: string = 'java.test.generateTests';
export const FIND_TEST_LOCATION: string = 'vscode.java.test.findTestLocation';
export const JAVA_TEST_OPEN_STACKTRACE: string = '_java.test.openStackTrace';
}
export namespace VSCodeCommands {
export const RUN_TESTS_IN_CURRENT_FILE: string = 'testing.runCurrentFile';
export const DEBUG_TESTS_IN_CURRENT_FILE: string = 'testing.debugCurrentFile';
export const REFRESH_TESTS: string = 'testing.refreshTests';
}
export namespace Configurations {
export const LOCAL_HOST: string = '127.0.0.1';
export const DEFAULT_CONFIG_NAME_SETTING_KEY: string = 'java.test.defaultConfig';
export const CONFIG_SETTING_KEY: string = 'java.test.config';
export const BUILTIN_CONFIG_NAME: string = 'default';
export const HINT_FOR_DEFAULT_CONFIG_SETTING_KEY: string = 'java.test.message.hintForSettingDefaultConfig';
}
export namespace Dialog {
export const NEVER_SHOW: string = 'Never Show again';
export const YES: string = 'Yes';
export const NO: string = 'No';
}
export namespace ExtensionName {
export const JAVA_DEBUGGER: string = 'vscjava.vscode-java-debug';
export const JAVA_LANGUAGE_SUPPORT: string = 'redhat.java';
}
export namespace Context {
export const ACTIVATION_CONTEXT_KEY: string = 'java:testRunnerActivated';
}
/**
* This is the prefix of the invocation test item's id.
* Invocation test items are created during test run.
* For example, the invocations from a parameterized test.
*/
export const INVOCATION_PREFIX: string = '[__INVOCATION__]-';

Просмотреть файл

@ -1,45 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export namespace JavaLanguageServerCommands {
export const EXECUTE_WORKSPACE_COMMAND: string = 'java.execute.workspaceCommand';
export const RESOLVE_STACKTRACE_LOCATION: string = 'java.project.resolveStackTraceLocation';
}
export namespace JavaTestRunnerDelegateCommands {
export const GET_TEST_SOURCE_PATH: string = 'vscode.java.test.get.testpath';
export const SEARCH_TEST_ITEMS: string = 'vscode.java.test.search.items';
export const SEARCH_TEST_ITEMS_ALL: string = 'vscode.java.test.search.items.all';
export const SEARCH_TEST_CODE_LENS: string = 'vscode.java.test.search.codelens';
export const SEARCH_TEST_LOCATION: string = 'vscode.java.test.search.location';
export const RESOLVE_JUNIT_ARGUMENT: string = 'vscode.java.test.junit.argument';
export const GENERATE_TESTS: string = 'vscode.java.test.generateTests';
}
export namespace JavaTestRunnerCommands {
export const OPEN_DOCUMENT: string = 'java.test.open.document';
export const REFRESH_EXPLORER: string = 'java.test.explorer.refresh';
export const RUN_TEST_FROM_CODELENS: string = 'java.test.run';
export const DEBUG_TEST_FROM_CODELENS: string = 'java.test.debug';
export const RUN_TEST_FROM_EXPLORER: string = 'java.test.explorer.run';
export const RUN_TEST_FROM_EDITOR: string = 'java.test.editor.run';
export const DEBUG_TEST_FROM_EDITOR: string = 'java.test.editor.debug';
export const DEBUG_ALL_TEST_FROM_EXPLORER: string = 'java.test.explorer.debugAll';
export const RUN_ALL_TEST_FROM_EXPLORER: string = 'java.test.explorer.runAll';
export const DEBUG_TEST_FROM_EXPLORER: string = 'java.test.explorer.debug';
export const RUN_TEST_FROM_JAVA_PROJECT_EXPLORER: string = 'java.test.runFromJavaProjectExplorer';
export const DEBUG_TEST_FROM_JAVA_PROJECT_EXPLORER: string = 'java.test.debugFromJavaProjectExplorer';
export const SHOW_TEST_REPORT: string = 'java.test.show.report';
export const SHOW_TEST_OUTPUT: string = 'java.test.show.output';
export const OPEN_TEST_LOG: string = 'java.test.open.log';
export const RELAUNCH_TESTS: string = 'java.test.relaunch';
export const JAVA_TEST_CANCEL: string = 'java.test.cancel';
export const JAVA_CONFIG_MIGRATE: string = 'java.test.config.migrate';
export const JAVA_TEST_REPORT_OPEN_STACKTRACE: string = 'java.test.report.openStackTrace';
export const JAVA_TEST_REPORT_OPEN_TEST_SOURCE_LOCATION: string = 'java.test.report.openTestSourceLocation';
export const JAVA_TEST_GENERATE_TESTS: string = 'java.test.generateTests';
}
export namespace VsCodeCommands {
export const VSCODE_OPEN: string = 'vscode.open';
}

Просмотреть файл

@ -1,34 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export const LOCAL_HOST: string = '127.0.0.1';
export const LOG_FILE_NAME: string = 'java_test_runner.log';
export const LOG_FILE_MAX_SIZE: number = 5 * 1024 * 1024;
export const LOG_FILE_MAX_NUMBER: number = 2;
export const LOG_LEVEL_SETTING_KEY: string = 'java.test.log.level';
export const DEFAULT_LOG_LEVEL: string = 'info';
export const DEFAULT_CONFIG_NAME_SETTING_KEY: string = 'java.test.defaultConfig';
export const CONFIG_SETTING_KEY: string = 'java.test.config';
export const BUILTIN_CONFIG_NAME: string = 'default';
export const REPORT_POSITION_SETTING_KEY: string = 'java.test.report.position';
export const DEFAULT_REPORT_POSITION: string = 'sideView';
export const ENABLE_EDITOR_SHORTCUTS_KEY: string = 'java.test.editor.enableShortcuts';
export const REPORT_SHOW_SETTING_KEY: string = 'java.test.report.showAfterExecution';
export const DEFAULT_REPORT_SHOW: string = 'onFailure';
export const HINT_FOR_DEPRECATED_CONFIG_SETTING_KEY: string = 'java.test.message.hintForDeprecatedConfig';
export const CONFIG_DOCUMENT_URL: string = 'https://aka.ms/java-test-config';
export const HINT_FOR_DEFAULT_CONFIG_SETTING_KEY: string = 'java.test.message.hintForSetingDefaultConfig';
export const ACTIVATION_CONTEXT_KEY: string = 'java:testRunnerActivated';
export enum ReportShowSetting {
Always = 'always',
OnFail = 'onFailure',
Never = 'never',
}

Просмотреть файл

@ -1,9 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export const LEARN_MORE: string = 'Learn More';
export const NEVER_SHOW: string = 'Never Show again';
export const YES: string = 'Yes';
export const NO: string = 'No';
export const OPEN_SETTING: string = 'Open Settings';
export const OPEN_OUTPUT_CHANNEL: string = 'Open Output Channel';

Просмотреть файл

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as LRUCache from 'lru-cache';
import { Uri } from 'vscode';
export const lruCache: LRUCache<Uri, MovingAverage> = new LRUCache<Uri, MovingAverage>(32);
// See: https://github.com/microsoft/vscode/blob/94c9ea46838a9a619aeafb7e8afd1170c967bb55/src/vs/base/common/numbers.ts
export class MovingAverage {
private _n: number = 1;
private _val: number = 0;
public update(value: number): this {
this._val = this._val + (value - this._val) / this._n;
this._n += 1;
return this;
}
public get value(): number {
return this._val;
}
}
export function getRequestDelay(uri: Uri): number {
const avg: MovingAverage | undefined = lruCache.get(uri);
if (!avg) {
return 350;
}
// See: https://github.com/microsoft/vscode/blob/94c9ea46838a9a619aeafb7e8afd1170c967bb55/src/vs/editor/common/modes/languageFeatureRegistry.ts#L204
return Math.max(350, Math.floor(1.3 * avg.value));
}

Просмотреть файл

@ -0,0 +1,426 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as _ from 'lodash';
import { CancellationToken, DebugConfiguration, Disposable, FileSystemWatcher, RelativePattern, TestController, TestItem, TestRun, TestRunProfileKind, TestRunRequest, tests, Uri, window, workspace, WorkspaceFolder } from 'vscode';
import { instrumentOperation, sendError, sendInfo } from 'vscode-extension-telemetry-wrapper';
import { INVOCATION_PREFIX } from '../constants';
import { IProgressReporter } from '../debugger.api';
import { extensionContext, isStandardServerReady, progressProvider } from '../extension';
import { testSourceProvider } from '../provider/testSourceProvider';
import { IExecutionConfig } from '../runConfigs';
import { BaseRunner } from '../runners/baseRunner/BaseRunner';
import { JUnitRunner } from '../runners/junitRunner/JunitRunner';
import { TestNGRunner } from '../runners/testngRunner/TestNGRunner';
import { IJavaTestItem, IRunTestContext, TestKind, TestLevel } from '../types';
import { loadRunConfig } from '../utils/configUtils';
import { resolveLaunchConfigurationForRunner } from '../utils/launchUtils';
import { dataCache, ITestItemData } from './testItemDataCache';
import { findDirectTestChildrenForClass, findTestPackagesAndTypes, findTestTypesAndMethods, loadJavaProjects, resolvePath, synchronizeItemsRecursively, updateItemForDocumentWithDebounce } 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);
};
testController.createRunProfile('Run Tests', TestRunProfileKind.Run, runHandler, true);
testController.createRunProfile('Debug Tests', TestRunProfileKind.Debug, runHandler, true);
startWatchingWorkspace();
}
export const loadChildren: (item: TestItem, token?: CancellationToken) => any = instrumentOperation('java.test.explorer.loadChildren', async (_operationId: string, item: TestItem, token?: CancellationToken) => {
if (!item) {
await loadJavaProjects();
return;
}
const data: ITestItemData | undefined = dataCache.get(item);
if (!data) {
return;
}
if (data.testLevel === TestLevel.Project) {
const packageAndTypes: IJavaTestItem[] = await findTestPackagesAndTypes(data.jdtHandler, token);
synchronizeItemsRecursively(item, packageAndTypes);
} else if (data.testLevel === TestLevel.Package) {
// unreachable code
} else if (data.testLevel === TestLevel.Class) {
if (!data.jdtHandler) {
sendError(new Error('The class node does not have jdt handler id.'));
return;
}
const testMethods: IJavaTestItem[] = await findDirectTestChildrenForClass(data.jdtHandler, token);
synchronizeItemsRecursively(item, testMethods);
}
});
async function startWatchingWorkspace(): Promise<void> {
if (!workspace.workspaceFolders) {
return;
}
for (const workspaceFolder of workspace.workspaceFolders) {
const patterns: RelativePattern[] = await testSourceProvider.getTestSourcePattern(workspaceFolder);
for (const pattern of patterns) {
const watcher: FileSystemWatcher = workspace.createFileSystemWatcher(pattern);
extensionContext.subscriptions.push(
watcher,
watcher.onDidCreate(async (uri: Uri) => {
const testTypes: IJavaTestItem[] = await findTestTypesAndMethods(uri.toString());
if (testTypes.length === 0) {
return;
}
await updateItemForDocumentWithDebounce(uri, testTypes);
}),
watcher.onDidChange(async (uri: Uri) => {
await updateItemForDocumentWithDebounce(uri);
}),
watcher.onDidDelete(async (uri: Uri) => {
const pathsData: IJavaTestItem[] = await resolvePath(uri.toString());
if (_.isEmpty(pathsData) || pathsData.length < 2) {
return;
}
const projectData: IJavaTestItem = pathsData[0];
if (projectData.testLevel !== TestLevel.Project) {
return;
}
const belongingProject: TestItem | undefined = testController?.items.get(projectData.id);
if (!belongingProject) {
return;
}
const packageData: IJavaTestItem = pathsData[1];
if (packageData.testLevel !== TestLevel.Package) {
return;
}
const belongingPackage: TestItem | undefined = belongingProject.children.get(packageData.id);
if (!belongingPackage) {
return;
}
belongingPackage.children.forEach((item: TestItem) => {
if (item.uri?.toString() === uri.toString()) {
belongingPackage.children.delete(item.id);
}
});
if (belongingPackage.children.size === 0) {
belongingProject.children.delete(belongingPackage.id);
}
}),
);
}
}
}
async function runHandler(request: TestRunRequest, token: CancellationToken): Promise<void> {
await runTests(request, { token, isDebug: !!request.profile?.label.includes('Debug') });
}
export const runTests: (request: TestRunRequest, option: IRunOption) => any = instrumentOperation('java.test.runTests', async (operationId: string, request: TestRunRequest, option: IRunOption) => {
sendInfo(operationId, {
isDebug: `${option.isDebug}`,
});
const testItems: TestItem[] = await new Promise<TestItem[]>(async (resolve: (result: TestItem[]) => void): Promise<void> => {
option.progressReporter = option.progressReporter ?? progressProvider?.createProgressReporter(option.isDebug ? 'Debug Tests' : 'Run Tests');
option.token?.onCancellationRequested(() => {
option.progressReporter?.done();
return resolve([]);
});
const progressToken: CancellationToken | undefined = option.progressReporter?.getCancellationToken();
option.onProgressCancelHandler = progressToken?.onCancellationRequested(() => {
option.progressReporter?.done();
return resolve([]);
});
option.progressReporter?.report('Searching tests...');
const result: TestItem[] = await getIncludedItems(request, progressToken);
await expandTests(result, TestLevel.Method, progressToken);
return resolve(result);
});
if (testItems.length === 0) {
option.progressReporter?.done();
return;
}
const run: TestRun = testController!.createTestRun(request);
try {
await new Promise<void>(async (resolve: () => void): Promise<void> => {
const token: CancellationToken = option.token ?? run.token;
token.onCancellationRequested(() => {
option.progressReporter?.done();
run.end();
return resolve();
});
enqueueTestMethods(testItems, run);
const queue: TestItem[][] = mergeTestMethods(testItems);
for (const testsInQueue of queue) {
if (testsInQueue.length === 0) {
continue;
}
const testProjectMapping: Map<string, TestItem[]> = mapTestItemsByProject(testsInQueue);
for (const [projectName, itemsPerProject] of testProjectMapping.entries()) {
const testKindMapping: Map<TestKind, TestItem[]> = mapTestItemsByKind(itemsPerProject);
for (const [kind, items] of testKindMapping.entries()) {
if (option.progressReporter?.isCancelled()) {
option.progressReporter = progressProvider?.createProgressReporter(option.isDebug ? 'Debug Tests' : 'Run Tests');
}
let delegatedToDebugger: boolean = false;
option.onProgressCancelHandler?.dispose();
option.progressReporter?.getCancellationToken().onCancellationRequested(() => {
if (delegatedToDebugger) {
// If the progress reporter has been delegated to debugger, a cancellation event
// might be emitted due to debug session finished, thus we will ignore such event.
return;
}
option.progressReporter?.done();
return resolve();
});
option.progressReporter?.report('Resolving launch configuration...');
// TODO: improve the config experience
const workspaceFolder: WorkspaceFolder | undefined = workspace.getWorkspaceFolder(items[0].uri!);
if (!workspaceFolder) {
window.showErrorMessage(`Failed to get workspace folder from test item: ${items[0].label}.`);
continue;
}
const config: IExecutionConfig | undefined = await loadRunConfig(workspaceFolder);
if (!config) {
continue;
}
const testContext: IRunTestContext = {
isDebug: option.isDebug,
kind,
projectName,
testItems: items,
testRun: run,
workspaceFolder,
};
const runner: BaseRunner | undefined = getRunnerByContext(testContext);
if (!runner) {
window.showErrorMessage(`Failed to get suitable runner for the test kind: ${testContext.kind}.`);
continue;
}
try {
await runner.setup();
const resolvedConfiguration: DebugConfiguration = option.launchConfiguration ?? await resolveLaunchConfigurationForRunner(runner, testContext, config);
resolvedConfiguration.__progressId = option.progressReporter?.getId();
delegatedToDebugger = true;
await runner.run(resolvedConfiguration, token, option.progressReporter);
} finally {
await runner.tearDown();
}
}
}
}
return resolve();
});
} finally {
run.end();
}
});
/**
* Set all the test item to queued state
*/
function enqueueTestMethods(testItems: TestItem[], run: TestRun): void {
const queuedTests: TestItem[] = [...testItems];
while (queuedTests.length) {
const queuedTest: TestItem = queuedTests.shift()!;
run.enqueued(queuedTest);
queuedTest.children.forEach((child: TestItem) => {
queuedTests.push(child);
});
}
}
/**
* Filter out the tests which are in the excluding list
* @param request the test run request
* @returns
*/
async function getIncludedItems(request: TestRunRequest, token?: CancellationToken): Promise<TestItem[]> {
let testItems: TestItem[] = [];
if (request.include) {
testItems.push(...request.include);
} else {
testController?.items.forEach((item: TestItem) => {
testItems.push(item);
});
}
if (testItems.length === 0) {
return [];
}
removeTestInvocations(testItems);
testItems = await expandTests(testItems, TestLevel.Class, token);
const excludingItems: TestItem[] = await expandTests(request.exclude || [], TestLevel.Class, token);
testItems = _.differenceBy(testItems, excludingItems, 'id');
return testItems;
}
/**
* Expand the test items to the target level
* @param testItems items to expand
* @param targetLevel target level to expand
*/
async function expandTests(testItems: TestItem[], targetLevel: TestLevel, token?: CancellationToken): Promise<TestItem[]> {
const results: Set<TestItem> = new Set();
const queue: TestItem[] = [...testItems];
while (queue.length) {
const item: TestItem = queue.shift()!;
const testLevel: TestLevel | undefined = dataCache.get(item)?.testLevel;
if (testLevel === undefined) {
continue;
}
if (testLevel >= targetLevel) {
results.add(item);
} else {
await loadChildren(item, token);
item.children.forEach((child: TestItem) => {
queue.push(child);
});
}
}
return Array.from(results);
}
/**
* Remove the test invocations since they might be changed
*/
function removeTestInvocations(testItems: TestItem[]): void {
const queue: TestItem[] = [...testItems];
while (queue.length) {
const item: TestItem = queue.shift()!;
if (item.id.startsWith(INVOCATION_PREFIX)) {
item.parent?.children.delete(item.id);
continue;
}
item.children.forEach((child: TestItem) => {
queue.push(child);
});
}
}
/**
* Eliminate the test methods if they are contained in the test class.
* Because the current test runner cannot run class and methods for the same time,
* in the returned array, all the classes are in one group and each method is a group.
*/
function mergeTestMethods(testItems: TestItem[]): TestItem[][] {
// tslint:disable-next-line: typedef
const classMapping: Map<string, TestItem> = testItems.reduce((map, i) => {
const testLevel: TestLevel | undefined = dataCache.get(i)?.testLevel;
if (testLevel === undefined) {
return map;
}
if (testLevel === TestLevel.Class) {
map.set(i.id, i);
}
return map;
}, new Map());
// tslint:disable-next-line: typedef
const testMapping: Map<TestItem, Set<TestItem>> = testItems.reduce((map, i) => {
const testLevel: TestLevel | undefined = dataCache.get(i)?.testLevel;
if (testLevel === undefined) {
return map;
}
if (testLevel !== TestLevel.Method) {
return map;
}
// skip the method if it's contained in test classes
if (classMapping.has(i.parent?.id || '')) {
return map;
}
const value: Set<TestItem> | undefined = map.get(i.parent);
if (value) {
value.add(i as TestItem);
} else {
map.set(i.parent, new Set([i]));
}
return map;
}, new Map());
const testMethods: TestItem[][] = [];
for (const [key, value] of testMapping) {
if (key.children.size === value.size) {
classMapping.set(key.id, key);
} else {
for (const method of value.values()) {
testMethods.push([method]);
}
}
}
return [[...classMapping.values()], ...testMethods];
}
function mapTestItemsByProject(items: TestItem[]): Map<string, TestItem[]> {
const map: Map<string, TestItem[]> = new Map<string, TestItem[]>();
for (const item of items) {
const projectName: string | undefined = dataCache.get(item)?.projectName;
if (!projectName) {
sendError(new Error('Item does not have project name.'));
continue;
}
const itemsPerProject: TestItem[] | undefined = map.get(projectName);
if (itemsPerProject) {
itemsPerProject.push(item);
} else {
map.set(projectName, [item]);
}
}
return map;
}
function mapTestItemsByKind(items: TestItem[]): Map<TestKind, TestItem[]> {
const map: Map<TestKind, TestItem[]> = new Map<TestKind, TestItem[]>();
for (const item of items) {
const testKind: TestKind | undefined = dataCache.get(item)?.testKind;
if (testKind === undefined) {
continue;
}
const itemsPerKind: TestItem[] | undefined = map.get(testKind);
if (itemsPerKind) {
itemsPerKind.push(item);
} else {
map.set(testKind, [item]);
}
}
return map;
}
function getRunnerByContext(testContext: IRunTestContext): BaseRunner | undefined {
switch (testContext.kind) {
case TestKind.JUnit:
case TestKind.JUnit5:
return new JUnitRunner(testContext);
case TestKind.TestNG:
return new TestNGRunner(testContext);
default:
return undefined;
}
}
interface IRunOption {
isDebug: boolean;
progressReporter?: IProgressReporter;
onProgressCancelHandler?: Disposable;
launchConfiguration?: DebugConfiguration;
token?: CancellationToken;
}

Просмотреть файл

@ -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;
}

262
src/controller/utils.ts Normal file
Просмотреть файл

@ -0,0 +1,262 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as _ from 'lodash';
import { performance } from 'perf_hooks';
import { CancellationToken, Range, TestItem, Uri, workspace, WorkspaceFolder } from 'vscode';
import { sendError } from 'vscode-extension-telemetry-wrapper';
import { INVOCATION_PREFIX, JavaTestRunnerDelegateCommands } from '../constants';
import { IJavaTestItem, TestLevel } from '../types';
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
import { getRequestDelay, lruCache, MovingAverage } from './debouncing';
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);
}
}
}
/**
* This method is used to synchronize the test items for the given parent node recursively. which means:
* - If an existing child is not contained in the childrenData parameter, it will be deleted
* - If a child does not exist, create it, otherwise, update it as well as its metadata.
*/
export function synchronizeItemsRecursively(parent: TestItem, childrenData: IJavaTestItem[] | undefined): void {
if (childrenData) {
// remove the out-of-date children
parent.children.forEach((child: TestItem) => {
if (child.id.startsWith(INVOCATION_PREFIX)) {
// only remove the invocation items before a new test session starts
return;
}
const existingItem: IJavaTestItem | undefined = childrenData.find((data: IJavaTestItem) => data.id === child.id);
if (!existingItem) {
parent.children.delete(child.id);
}
});
// update/create children
for (const child of childrenData) {
const childItem: TestItem = updateOrCreateTestItem(parent, child);
if (child.testLevel <= TestLevel.Class) {
childItem.canResolveChildren = true;
}
synchronizeItemsRecursively(childItem, child.children);
}
}
}
function updateOrCreateTestItem(parent: TestItem, childData: IJavaTestItem): TestItem {
let childItem: TestItem | undefined = parent.children.get(childData.id);
if (childItem) {
updateTestItem(childItem, childData);
} else {
childItem = createTestItem(childData, parent);
}
return childItem;
}
function updateTestItem(testItem: TestItem, metaInfo: IJavaTestItem): void {
testItem.range = asRange(metaInfo.range);
if (metaInfo.testLevel !== TestLevel.Invocation) {
dataCache.set(testItem, {
jdtHandler: metaInfo.jdtHandler,
fullName: metaInfo.fullName,
projectName: metaInfo.projectName,
testLevel: metaInfo.testLevel,
testKind: metaInfo.testKind,
});
}
}
/**
* 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;
}
let updateNodeForDocumentTimeout: NodeJS.Timer;
/**
* Update test item in a document with adaptive debounce enabled.
* @param uri uri of the document
* @param testTypes test metadata
*/
export async function updateItemForDocumentWithDebounce(uri: Uri, testTypes?: IJavaTestItem[]): Promise<TestItem[]> {
if (updateNodeForDocumentTimeout) {
clearTimeout(updateNodeForDocumentTimeout);
}
const timeout: number = getRequestDelay(uri);
return new Promise<TestItem[]>((resolve: (items: TestItem[]) => void): void => {
updateNodeForDocumentTimeout = setTimeout(async () => {
const startTime: number = performance.now();
const result: TestItem[] = await updateItemForDocument(uri, testTypes);
const executionTime: number = performance.now() - startTime;
const movingAverage: MovingAverage = lruCache.get(uri) || new MovingAverage();
movingAverage.update(executionTime);
lruCache.set(uri, movingAverage);
return resolve(result);
}, timeout);
});
}
/**
* Update test item in a document immediately.
* @param uri uri of the document
* @param testTypes test metadata
*/
export async function updateItemForDocument(uri: Uri, testTypes?: IJavaTestItem[]): Promise<TestItem[]> {
testTypes = testTypes ?? await findTestTypesAndMethods(uri.toString());
if (testTypes.length === 0) {
return [];
}
const belongingPackage: TestItem | undefined = findBelongingPackageItem(testTypes[0])
|| await resolveBelongingPackage(uri);
if (!belongingPackage) {
sendError(new Error('Failed to find the belonging package'));
return [];
}
const tests: TestItem[] = [];
for (const testType of testTypes) {
// here we do not directly call synchronizeItemsRecursively() because testTypes here are just part of the
// children of the belonging package, we don't want to delete other children unexpectedly.
let testTypeItem: TestItem | undefined = belongingPackage.children.get(testType.id);
if (!testTypeItem) {
testTypeItem = createTestItem(testType, belongingPackage);
testTypeItem.canResolveChildren = true;
} else {
updateTestItem(testTypeItem, testType);
}
tests.push(testTypeItem);
synchronizeItemsRecursively(testTypeItem, testType.children);
}
return tests;
}
/**
* Give a test item for a type, find its belonging package item according to its id.
*/
function findBelongingPackageItem(testType: IJavaTestItem): TestItem | undefined {
const indexOfProjectSeparator: number = testType.id.indexOf('@');
if (indexOfProjectSeparator < 0) {
return undefined;
}
const projectId: string = testType.id.substring(0, indexOfProjectSeparator);
const projectItem: TestItem | undefined = testController?.items.get(projectId);
if (!projectItem) {
return undefined;
}
const indexOfPackageSeparator: number = testType.id.lastIndexOf('.');
const packageId: string = testType.id.substring(indexOfProjectSeparator + 1, indexOfPackageSeparator);
const packageItem: TestItem | undefined = projectItem.children.get(`${projectId}@${packageId}`);
return packageItem;
}
/**
* Give a document uri, resolve its belonging package item.
*/
async function resolveBelongingPackage(uri: Uri): Promise<TestItem | undefined> {
const pathsData: IJavaTestItem[] = await resolvePath(uri.toString());
if (_.isEmpty(pathsData) || pathsData.length < 2) {
return undefined;
}
const projectData: IJavaTestItem = pathsData[0];
if (projectData.testLevel !== TestLevel.Project) {
return undefined;
}
let belongingProject: TestItem | undefined = testController?.items.get(projectData.id);
if (!belongingProject) {
belongingProject = createTestItem(projectData);
testController?.items.add(belongingProject);
belongingProject.canResolveChildren = true;
}
const packageData: IJavaTestItem = pathsData[1];
if (packageData.testLevel !== TestLevel.Package) {
return undefined;
}
let belongingPackage: TestItem | undefined = belongingProject.children.get(packageData.id);
if (!belongingPackage) {
belongingPackage = createTestItem(packageData, belongingProject);
belongingPackage.canResolveChildren = true;
}
return belongingPackage;
}
/**
* Parse the range object with server mode to client format
* @param range range with server side format
*/
export function asRange(range: any): Range | undefined {
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) || [];
}
export async function findTestPackagesAndTypes(handlerId: string, token?: CancellationToken): Promise<IJavaTestItem[]> {
return await executeJavaLanguageServerCommand<IJavaTestItem[]>(
JavaTestRunnerDelegateCommands.FIND_TEST_PACKAGES_AND_TYPES, handlerId, token) || [];
}
export async function findDirectTestChildrenForClass(handlerId: string, token?: CancellationToken): Promise<IJavaTestItem[]> {
return await executeJavaLanguageServerCommand<IJavaTestItem[]>(
JavaTestRunnerDelegateCommands.FIND_DIRECT_CHILDREN_FOR_CLASS, handlerId, token) || [];
}
export async function findTestTypesAndMethods(uri: string, token?: CancellationToken): Promise<IJavaTestItem[]> {
return await executeJavaLanguageServerCommand<IJavaTestItem[]>(
JavaTestRunnerDelegateCommands.FIND_TEST_TYPES_AND_METHODS, uri, token) || [];
}
export async function resolvePath(uri: string): Promise<IJavaTestItem[]> {
return await executeJavaLanguageServerCommand<IJavaTestItem[]>(
JavaTestRunnerDelegateCommands.RESOLVE_PATH, uri) || [];
}

Просмотреть файл

@ -1,140 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import { Command, Disposable, Event, EventEmitter, ExtensionContext, extensions, Range, ThemeColor, ThemeIcon, TreeDataProvider, TreeItem, TreeItemCollapsibleState, Uri, workspace, WorkspaceFolder } from 'vscode';
import { VsCodeCommands } from '../constants/commands';
import { isLightWeightMode, isSwitchingServer } from '../extension';
import { ITestItem, TestKind, TestLevel } from '../protocols';
import { ITestResult, TestStatus } from '../runners/models';
import { testFileWatcher } from '../testFileWatcher';
import { testItemModel } from '../testItemModel';
import { testResultManager } from '../testResultManager';
export class TestExplorer implements TreeDataProvider<ITestItem>, Disposable {
public readonly testExplorerViewId: string = 'testExplorer';
private onDidChangeTreeDataEventEmitter: EventEmitter<ITestItem | null| undefined> = new EventEmitter<ITestItem | null | undefined>();
// tslint:disable-next-line:member-ordering
public readonly onDidChangeTreeData: Event<ITestItem | null | undefined> = this.onDidChangeTreeDataEventEmitter.event;
private _context: ExtensionContext;
public initialize(context: ExtensionContext): void {
this._context = context;
}
public getTreeItem(element: ITestItem): TreeItem | Thenable<TreeItem> {
return {
label: element.displayName,
collapsibleState: this.resolveCollapsibleState(element),
command: this.resolveCommand(element),
iconPath: this.resolveIconPath(element),
contextValue: element.level.toString(),
};
}
public async getChildren(element?: ITestItem): Promise<ITestItem[]> {
if (isLightWeightMode()) {
return [];
}
if (isSwitchingServer()) {
await new Promise<void>((resolve: () => void): void => {
extensions.getExtension('redhat.java')!.exports.onDidServerModeChange(resolve);
});
}
let nodes: ITestItem[] = [];
if (!element) {
nodes = this.getWorkspaceFolders();
} else {
nodes = await testItemModel.getNodeChildren(element);
}
return nodes.sort((a: ITestItem, b: ITestItem) => a.displayName.localeCompare(b.displayName));
}
public refresh(element?: ITestItem): void {
this.onDidChangeTreeDataEventEmitter.fire(element);
if (!element) {
testFileWatcher.registerListeners();
}
}
public dispose(): void {
this.onDidChangeTreeDataEventEmitter.dispose();
}
private getWorkspaceFolders(): ITestItem[] {
let results: ITestItem[] = [];
if (workspace.workspaceFolders) {
results = workspace.workspaceFolders.map((folder: WorkspaceFolder) => {
return {
id: folder.uri.fsPath,
displayName: folder.name,
fullName: folder.name,
kind: TestKind.None,
project: '',
level: TestLevel.Folder,
location: {
uri: folder.uri.toString(),
range: new Range(0, 0, 0, 0),
},
children: undefined,
};
});
}
return results;
}
private resolveCollapsibleState(element: ITestItem): TreeItemCollapsibleState {
if (element.level === TestLevel.Method) {
return TreeItemCollapsibleState.None;
}
return TreeItemCollapsibleState.Collapsed;
}
private resolveIconPath(element: ITestItem): undefined | { dark: string | Uri, light: string | Uri } | ThemeIcon {
switch (element.level) {
case TestLevel.Method:
const result: ITestResult | undefined = testResultManager.getResultById(element.id);
if (result) {
switch (result.status) {
case TestStatus.Pass:
return new ThemeIcon('pass', new ThemeColor('charts.green'));
case TestStatus.Fail:
return new ThemeIcon('error', new ThemeColor('charts.red'));
case TestStatus.Running:
return {
dark: this._context.asAbsolutePath(path.join('resources', 'media', 'dark', 'running.svg')),
light: this._context.asAbsolutePath(path.join('resources', 'media', 'light', 'running.svg')),
};
case TestStatus.Pending:
return new ThemeIcon('history');
default:
break;
}
}
return new ThemeIcon('symbol-method');
case TestLevel.Class:
return new ThemeIcon('symbol-class');
case TestLevel.Package:
return new ThemeIcon('symbol-package');
default:
return undefined;
}
}
private resolveCommand(element: ITestItem): Command | undefined {
if (element.level >= TestLevel.Class) {
return {
command: VsCodeCommands.VSCODE_OPEN,
title: 'Open File',
arguments: [Uri.parse(element.location.uri), { preserveFocus: true, selection: element.location.range }],
};
}
return undefined;
}
}
export const testExplorer: TestExplorer = new TestExplorer();

Просмотреть файл

@ -1,60 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import { commands, DebugConfiguration, Event, Extension, ExtensionContext, extensions, Range, TreeView, TreeViewExpansionEvent, TreeViewSelectionChangeEvent, Uri, window, workspace } from 'vscode';
import { commands, DebugConfiguration, Event, Extension, ExtensionContext, extensions, TestItem, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, window, workspace } from 'vscode';
import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentOperation, instrumentOperationAsVsCodeCommand } from 'vscode-extension-telemetry-wrapper';
import { sendInfo } from 'vscode-extension-telemetry-wrapper';
import { testSourceProvider } from '../extension.bundle';
import { testCodeLensController } from './codelens/TestCodeLensController';
import { debugTestsFromExplorer, openTextDocument, runTestsFromExplorer, runTestsFromJavaProjectExplorer } from './commands/explorerCommands';
import { generateTests, registerAdvanceAskForChoice, registerAskForChoiceCommand, registerAskForInputCommand } from './commands/generationCommands';
import { openLogFile, showOutputChannel } from './commands/logCommands';
import { runFromCodeLens } from './commands/runFromCodeLens';
import { executeTestsFromUri } from './commands/runFromUri';
import { openStackTrace, openTestSourceLocation } from './commands/testReportCommands';
import { JavaTestRunnerCommands } from './constants/commands';
import { ACTIVATION_CONTEXT_KEY } from './constants/configs';
import { IProgressProvider, IProgressReporter } from './debugger.api';
import { runTestsFromJavaProjectExplorer } from './commands/projectExplorerCommands';
import { refresh, runTestsFromTestExplorer } from './commands/testExplorerCommands';
import { openStackTrace } from './commands/testReportCommands';
import { Context, ExtensionName, JavaTestRunnerCommands, VSCodeCommands } from './constants';
import { createTestController, testController } from './controller/testController';
import { updateItemForDocument, updateItemForDocumentWithDebounce } from './controller/utils';
import { IProgressProvider } from './debugger.api';
import { initExpService } from './experimentationService';
import { testExplorer } from './explorer/testExplorer';
import { logger } from './logger/logger';
import { ITestItem } from './protocols';
import { disposeCodeActionProvider, registerTestCodeActionProvider } from './provider/codeActionProvider';
import { ITestResult } from './runners/models';
import { runnerScheduler } from './runners/runnerScheduler';
import { testFileWatcher } from './testFileWatcher';
import { testItemModel } from './testItemModel';
import { testReportProvider } from './testReportProvider';
import { testResultManager } from './testResultManager';
import { testStatusBarProvider } from './testStatusBarProvider';
import { migrateTestConfig } from './utils/configUtils';
import { testSourceProvider } from './provider/testSourceProvider';
export let extensionContext: ExtensionContext;
export async function activate(context: ExtensionContext): Promise<void> {
extensionContext = context;
await initializeFromJsonFile(context.asAbsolutePath('./package.json'), { firstParty: true });
await initExpService(context);
await instrumentOperation('activation', doActivate)(context);
await commands.executeCommand('setContext', ACTIVATION_CONTEXT_KEY, true);
await commands.executeCommand('setContext', Context.ACTIVATION_CONTEXT_KEY, true);
}
export async function deactivate(): Promise<void> {
sendInfo('treeViewEventSummary', EventCounter.dict);
testFileWatcher.dispose();
testCodeLensController.dispose();
disposeCodeActionProvider();
await disposeTelemetryWrapper();
await runnerScheduler.cleanUp(false /* isCancel */);
testController?.dispose();
}
async function doActivate(_operationId: string, context: ExtensionContext): Promise<void> {
const storagePath: string = context.storageUri?.fsPath || path.join(os.tmpdir(), 'java_test_runner');
await fse.ensureDir(storagePath);
logger.initialize(storagePath, context.subscriptions);
const javaLanguageSupport: Extension<any> | undefined = extensions.getExtension('redhat.java');
let javaLanguageSupportVersion: string = '0.0.0';
const javaLanguageSupport: Extension<any> | undefined = extensions.getExtension(ExtensionName.JAVA_LANGUAGE_SUPPORT);
if (javaLanguageSupport?.isActive) {
const extensionApi: any = javaLanguageSupport.exports;
if (!extensionApi) {
@ -66,9 +45,8 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom
if (extensionApi.onDidClasspathUpdate) {
const onDidClasspathUpdate: Event<Uri> = extensionApi.onDidClasspathUpdate;
context.subscriptions.push(onDidClasspathUpdate(async () => {
await testSourceProvider.initialize();
await testFileWatcher.registerListeners(true /*enableDebounce*/);
await testCodeLensController.registerCodeLensProvider();
testSourceProvider.clear();
commands.executeCommand(VSCodeCommands.REFRESH_TESTS);
}));
}
@ -78,14 +56,13 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom
if (serverMode === mode) {
return;
}
// Only re-initialize the component when its lightweight -> standard
serverMode = mode;
if (serverMode !== LanguageServerMode.Hybrid) {
testExplorer.refresh();
await testSourceProvider.initialize();
await testFileWatcher.registerListeners();
await testCodeLensController.registerCodeLensProvider();
await registerTestCodeActionProvider();
// Only re-initialize the component when its lightweight -> standard
if (mode === LanguageServerMode.Standard) {
testSourceProvider.clear();
registerTestCodeActionProvider();
createTestController();
await showTestItemsInCurrentFile();
}
}));
}
@ -93,80 +70,69 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom
if (extensionApi.onDidProjectsImport) {
const onDidProjectsImport: Event<Uri[]> = extensionApi.onDidProjectsImport;
context.subscriptions.push(onDidProjectsImport(async () => {
await testSourceProvider.initialize();
await testFileWatcher.registerListeners(true /*enableDebounce*/);
await testCodeLensController.registerCodeLensProvider();
testSourceProvider.clear();
commands.executeCommand(VSCodeCommands.REFRESH_TESTS);
}));
}
javaLanguageSupportVersion = javaLanguageSupport.packageJSON.version;
}
const javaDebugger: Extension<any> | undefined = extensions.getExtension('vscjava.vscode-java-debug');
const javaDebugger: Extension<any> | undefined = extensions.getExtension(ExtensionName.JAVA_DEBUGGER);
if (javaDebugger?.isActive) {
progressProvider = javaDebugger.exports?.progressProvider;
}
testExplorer.initialize(context);
const testTreeView: TreeView<ITestItem> = window.createTreeView(testExplorer.testExplorerViewId, { treeDataProvider: testExplorer, showCollapseAll: true });
runnerScheduler.initialize(context);
testReportProvider.initialize(context, javaLanguageSupportVersion);
registerAskForChoiceCommand(context);
registerAdvanceAskForChoice(context);
registerAskForInputCommand(context);
if (isStandardServerReady()) {
await testSourceProvider.initialize();
await testFileWatcher.registerListeners();
await testCodeLensController.registerCodeLensProvider();
await registerTestCodeActionProvider();
}
context.subscriptions.push(
testExplorer,
testTreeView,
testStatusBarProvider,
testResultManager,
testReportProvider,
testFileWatcher,
logger,
testCodeLensController,
testItemModel,
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.OPEN_DOCUMENT, async (uri: Uri, range?: Range) => await openTextDocument(uri, range)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.REFRESH_EXPLORER, (node: ITestItem) => testExplorer.refresh(node)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_TEST_FROM_CODELENS, async (test: ITestItem) => await runFromCodeLens(test, false /* isDebug */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_CODELENS, async (test: ITestItem) => await runFromCodeLens(test, true /* isDebug */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_ALL_TEST_FROM_EXPLORER, async () => await runTestsFromExplorer()),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_ALL_TEST_FROM_EXPLORER, async () => await debugTestsFromExplorer()),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_TEST_FROM_EXPLORER, async (node?: ITestItem, launchConfiguration?: DebugConfiguration) => await runTestsFromExplorer(node, launchConfiguration)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_EXPLORER, async (node?: ITestItem, launchConfiguration?: DebugConfiguration) => await debugTestsFromExplorer(node, launchConfiguration)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RELAUNCH_TESTS, async () => await runnerScheduler.relaunch()),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.SHOW_TEST_REPORT, async (tests?: ITestResult[]) => await testReportProvider.report(tests)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.SHOW_TEST_OUTPUT, () => showOutputChannel()),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.OPEN_TEST_LOG, async () => await openLogFile(storagePath)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_TEST_CANCEL, async () => await runnerScheduler.cleanUp(true /* isCancel */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_CONFIG_MIGRATE, async () => await migrateTestConfig()),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_TEST_FROM_EDITOR, async (uri?: Uri, progressReporter?: IProgressReporter) => await executeTestsFromUri(uri, progressReporter, false /* isDebug */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_EDITOR, async (uri?: Uri, progressReporter?: IProgressReporter) => await executeTestsFromUri(uri, progressReporter, true /* isDebug */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_TEST_REPORT_OPEN_STACKTRACE, async (trace: string, fullName: string) => await openStackTrace(trace, fullName)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_TEST_REPORT_OPEN_TEST_SOURCE_LOCATION, async (uri: string, range: string, fullName: string) => await openTestSourceLocation(uri, range, fullName)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_TEST_OPEN_STACKTRACE, openStackTrace),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_TEST_FROM_EDITOR, async () => await commands.executeCommand(VSCodeCommands.RUN_TESTS_IN_CURRENT_FILE)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_EDITOR, async () => await commands.executeCommand(VSCodeCommands.DEBUG_TESTS_IN_CURRENT_FILE)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_TEST_GENERATE_TESTS, ((uri: Uri, startPosition: number) => generateTests(uri, startPosition))),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_FROM_TEST_EXPLORER, async (node: TestItem, launchConfiguration: DebugConfiguration) => await runTestsFromTestExplorer(node, launchConfiguration, false)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_FROM_TEST_EXPLORER, async (node: TestItem, launchConfiguration: DebugConfiguration) => await runTestsFromTestExplorer(node, launchConfiguration, false)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.REFRESH_TEST_EXPLORER, async () => await refresh()),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.RUN_TEST_FROM_JAVA_PROJECT_EXPLORER, async (node: any) => await runTestsFromJavaProjectExplorer(node, false /* isDebug */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.DEBUG_TEST_FROM_JAVA_PROJECT_EXPLORER, async (node: any) => await runTestsFromJavaProjectExplorer(node, true /* isDebug */)),
instrumentOperationAsVsCodeCommand(JavaTestRunnerCommands.JAVA_TEST_GENERATE_TESTS, ((uri: Uri, startPosition: number) => generateTests(uri, startPosition))),
window.onDidChangeActiveTextEditor(async (e: TextEditor | undefined) => {
if (e?.document) {
if (!isJavaFile(e.document)) {
return;
}
// track the test tree view events.
testTreeView.onDidChangeSelection((_e: TreeViewSelectionChangeEvent<ITestItem>) => {
EventCounter.increase('didChangeSelection');
if (!await testSourceProvider.isOnTestSourcePath(e.document.uri)) {
return;
}
await updateItemForDocumentWithDebounce(e.document.uri);
}
}),
testTreeView.onDidCollapseElement((_e: TreeViewExpansionEvent<ITestItem>) => {
EventCounter.increase('didCollapseElement');
}),
testTreeView.onDidExpandElement((_e: TreeViewExpansionEvent<ITestItem>) => {
EventCounter.increase('didExpandElement');
workspace.onDidChangeTextDocument(async (e: TextDocumentChangeEvent) => {
if (!isJavaFile(e.document)) {
return;
}
if (!await testSourceProvider.isOnTestSourcePath(e.document.uri)) {
return;
}
await updateItemForDocumentWithDebounce(e.document.uri);
}),
);
setContextKeyForDeprecatedConfig();
if (isStandardServerReady()) {
registerTestCodeActionProvider();
createTestController();
}
await showTestItemsInCurrentFile();
}
async function showTestItemsInCurrentFile(): Promise<void> {
if (window.activeTextEditor && isJavaFile(window.activeTextEditor.document) &&
await testSourceProvider.isOnTestSourcePath(window.activeTextEditor.document.uri)) {
// we didn't call the debounced version to avoid first call takes a long time and expand too much
// for the debounce window. (cpu resources are limited during activation)
await updateItemForDocument(window.activeTextEditor.document.uri);
}
}
export function isStandardServerReady(): boolean {
@ -182,22 +148,6 @@ export function isStandardServerReady(): boolean {
return true;
}
export function isLightWeightMode(): boolean {
return serverMode === LanguageServerMode.LightWeight;
}
export function isSwitchingServer(): boolean {
return serverMode === LanguageServerMode.Hybrid;
}
async function setContextKeyForDeprecatedConfig(): Promise<void> {
const deprecatedConfigs: Uri[] = await workspace.findFiles('**/.vscode/launch.test.json', null, 1 /*maxResult*/);
if (deprecatedConfigs.length > 0) {
commands.executeCommand('setContext', 'java:hasDeprecatedTestConfig', true);
sendInfo('', { hasDeprecatedTestConfig: 'true' });
}
}
let serverMode: string | undefined;
const enum LanguageServerMode {
@ -208,11 +158,6 @@ const enum LanguageServerMode {
export let progressProvider: IProgressProvider | undefined;
class EventCounter {
public static dict: {[key: string]: number} = {};
public static increase(event: string): void {
const count: number = this.dict[event] ?? 0;
this.dict[event] = count + 1;
}
function isJavaFile(document: TextDocument): boolean {
return path.extname(document.fileName) === '.java';
}

Просмотреть файл

@ -1,58 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import { ConfigurationChangeEvent, Disposable, workspace } from 'vscode';
import * as winston from 'winston';
import { LOG_FILE_MAX_NUMBER, LOG_FILE_MAX_SIZE, LOG_FILE_NAME, LOG_LEVEL_SETTING_KEY } from '../constants/configs';
import { getLogLevel } from '../utils/settingUtils';
import { outputChannelTransport } from './outputChannelTransport';
class Logger implements Disposable {
private logger: winston.Logger;
public initialize(storagePath: string, disposables: Disposable[]): void {
this.logger = winston.createLogger({
transports: [
new (winston.transports.File)({
level: getLogLevel(),
filename: path.join(storagePath, LOG_FILE_NAME),
maxsize: LOG_FILE_MAX_SIZE,
maxFiles: LOG_FILE_MAX_NUMBER,
tailable: true,
}),
outputChannelTransport,
],
});
workspace.onDidChangeConfiguration((e: ConfigurationChangeEvent) => {
if (e.affectsConfiguration(LOG_LEVEL_SETTING_KEY)) {
const logLevel: string = getLogLevel();
for (const transport of this.logger.transports) {
transport.level = logLevel;
}
}
}, null, disposables);
}
public dispose(): void {
for (const transport of this.logger.transports) {
if (transport.close) {
transport.close();
}
}
}
public verbose(message: string): void {
this.logger.verbose(message);
}
public info(message: string): void {
this.logger.info(message);
}
public error(message: string, error?: Error): void {
this.logger.error(`${message}.${error ? ' ' + error : ''}`);
}
}
export const logger: Logger = new Logger();

Просмотреть файл

@ -1,33 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { TransformableInfo } from 'logform';
import { OutputChannel, window } from 'vscode';
import * as Transport from 'winston-transport';
import { getLogLevel } from '../utils/settingUtils';
class OutputChannelTransport extends Transport {
private channel: OutputChannel;
constructor(options: Transport.TransportStreamOptions) {
super(options);
this.channel = window.createOutputChannel('Java Test Runner');
}
public log(msg: TransformableInfo, next?: () => void): any {
this.channel.append(msg.message);
if (next) {
next();
}
}
public close(): void {
this.channel.dispose();
}
public show(): void {
this.channel.show();
}
}
export const outputChannelTransport: OutputChannelTransport = new OutputChannelTransport({level: getLogLevel()});

Просмотреть файл

@ -1,42 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Range } from 'vscode';
export interface ILocation {
uri: string;
range: Range;
}
export interface ITestItem {
id: string;
displayName: string;
fullName: string;
children: string[] | undefined;
kind: TestKind;
project: string;
level: TestLevel;
location: ILocation;
}
export interface ISearchTestItemParams {
level: TestLevel;
fullName: string;
uri: string;
isHierarchicalPackage?: boolean;
}
export enum TestLevel {
Root = 0,
Folder = 1,
Package = 2,
Class = 3,
Method = 4,
}
export enum TestKind {
JUnit5 = 0,
JUnit = 1,
TestNG = 2,
None = 100,
}

Просмотреть файл

@ -1,24 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import { RelativePattern, Uri, workspace, WorkspaceFolder } from 'vscode';
import { getTestSourcePaths } from '../utils/commandUtils';
import { JavaTestRunnerDelegateCommands } from '../constants';
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
class TestSourcePathProvider {
private testSource: ITestSourcePath[];
private testSourceMapping: Map<Uri, ITestSourcePath[]> = new Map();
public async initialize(): Promise<void> {
this.testSource = [];
if (!workspace.workspaceFolders) {
return;
}
this.testSource = await getTestSourcePaths(workspace.workspaceFolders.map((workspaceFolder: WorkspaceFolder) => workspaceFolder.uri.toString()));
}
public async getTestSourcePattern(containsGeneral: boolean = true): Promise<RelativePattern[]> {
public async getTestSourcePattern(workspaceFolder: WorkspaceFolder, containsGeneral: boolean = true): Promise<RelativePattern[]> {
const patterns: RelativePattern[] = [];
const sourcePaths: string[] = await testSourceProvider.getTestSourcePath(containsGeneral);
const sourcePaths: string[] = await testSourceProvider.getTestSourcePath(workspaceFolder, containsGeneral);
for (const sourcePath of sourcePaths) {
const normalizedPath: string = Uri.file(sourcePath).fsPath;
const pattern: RelativePattern = new RelativePattern(normalizedPath, '**/*.java');
@ -27,21 +20,53 @@ class TestSourcePathProvider {
return patterns;
}
public async getTestSourcePath(containsGeneral: boolean = true): Promise<string[]> {
if (this.testSource === undefined) {
await this.initialize();
}
public async getTestSourcePath(workspaceFolder: WorkspaceFolder, containsGeneral: boolean = true): Promise<string[]> {
const testPaths: ITestSourcePath[] = await this.getTestPaths(workspaceFolder);
if (containsGeneral) {
return this.testSource.map((s: ITestSourcePath) => s.testSourcePath);
return testPaths.map((s: ITestSourcePath) => s.testSourcePath);
}
return this.testSource.filter((s: ITestSourcePath) => s.isStrict)
return testPaths.filter((s: ITestSourcePath) => s.isStrict)
.map((s: ITestSourcePath) => s.testSourcePath);
}
public async isOnTestSourcePath(uri: Uri): Promise<boolean> {
const workspaceFolder: WorkspaceFolder | undefined = workspace.getWorkspaceFolder(uri);
if (!workspaceFolder) {
return false;
}
const testPaths: ITestSourcePath[] = await this.getTestPaths(workspaceFolder);
const fsPath: string = uri.fsPath;
for (const testPath of testPaths) {
const relativePath: string = path.relative(testPath.testSourcePath, fsPath);
if (!relativePath.startsWith('..')) {
return true;
}
}
return false;
}
public clear(): void {
this.testSourceMapping.clear();
}
private async getTestPaths(workspaceFolder: WorkspaceFolder): Promise<ITestSourcePath[]> {
let testPaths: ITestSourcePath[] | undefined = this.testSourceMapping.get(workspaceFolder.uri);
if (!testPaths) {
testPaths = await getTestSourcePaths([workspaceFolder.uri.toString()]);
this.testSourceMapping.set(workspaceFolder.uri, testPaths);
}
return testPaths;
}
}
export interface ITestSourcePath {
async function getTestSourcePaths(uri: string[]): Promise<ITestSourcePath[]> {
return await executeJavaLanguageServerCommand<ITestSourcePath[]>(
JavaTestRunnerDelegateCommands.GET_TEST_SOURCE_PATH, uri) || [];
}
interface ITestSourcePath {
testSourcePath: string;
/**
* All the source paths from eclipse and invisible project will be treated as test source

Просмотреть файл

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { BUILTIN_CONFIG_NAME } from './constants/configs';
import { Configurations } from './constants';
export interface IExecutionConfig {
name?: string;
@ -14,21 +14,11 @@ export interface IExecutionConfig {
sourcePaths?: string[];
}
export interface IExecutionConfigGroup {
default: string;
items: IExecutionConfig[];
}
export interface ITestConfig {
run: IExecutionConfigGroup;
debug: IExecutionConfigGroup;
}
export function getBuiltinConfig(): IExecutionConfig {
return Object.assign({}, BUILTIN_CONFIG);
}
const BUILTIN_CONFIG: IExecutionConfig = {
name: BUILTIN_CONFIG_NAME,
name: Configurations.BUILTIN_CONFIG_NAME,
workingDirectory: '${workspaceFolder}',
};

Просмотреть файл

@ -1,12 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { DebugConfiguration } from 'vscode';
import { CancellationToken, DebugConfiguration } from 'vscode';
import { IProgressReporter } from '../debugger.api';
import { IRunnerContext } from './models';
import { IRunTestContext } from '../types';
export interface ITestRunner {
setup(context: IRunnerContext): Promise<void>;
run(launchConfiguration: DebugConfiguration, progressReporter?: IProgressReporter): Promise<Set<string>>;
setup(context: IRunTestContext): Promise<void>;
run(launchConfiguration: DebugConfiguration, token: CancellationToken, progressReporter?: IProgressReporter): Promise<void>;
tearDown(isCancel: boolean): Promise<void>;
}

Просмотреть файл

@ -1,47 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as fse from 'fs-extra';
import { default as getPort } from 'get-port';
import * as iconv from 'iconv-lite';
import { AddressInfo, createServer, Server, Socket } from 'net';
import * as os from 'os';
import * as path from 'path';
import { debug, DebugConfiguration, DebugSession, Disposable, Uri, workspace } from 'vscode';
import { LOCAL_HOST } from '../../constants/configs';
import { CancellationToken, debug, DebugConfiguration, DebugSession, Disposable } from 'vscode';
import { sendError } from 'vscode-extension-telemetry-wrapper';
import { Configurations } from '../../constants';
import { IProgressReporter } from '../../debugger.api';
import { logger } from '../../logger/logger';
import { ITestItem, TestLevel } from '../../protocols';
import { IExecutionConfig } from '../../runConfigs';
import { testResultManager } from '../../testResultManager';
import { IRunTestContext } from '../../types';
import { ITestRunner } from '../ITestRunner';
import { IRunnerContext, ITestResult, TestStatus } from '../models';
import { BaseRunnerResultAnalyzer } from './BaseRunnerResultAnalyzer';
import { IRunnerResultAnalyzer } from './IRunnerResultAnalyzer';
export abstract class BaseRunner implements ITestRunner {
protected testIds: string[];
protected context: IRunnerContext;
protected server: Server;
protected socket: Socket;
protected runnerResultAnalyzer: BaseRunnerResultAnalyzer;
protected runnerResultAnalyzer: IRunnerResultAnalyzer;
private disposables: Disposable[] = [];
constructor(
protected extensionPath: string) {}
constructor(protected testContext: IRunTestContext) {}
public async setup(context: IRunnerContext): Promise<void> {
this.context = context;
public async setup(): Promise<void> {
await this.startSocketServer();
const flattenedTestIds: string[] = [];
for (const test of context.tests) {
this.flattenTestIds(test, flattenedTestIds);
}
this.testIds = flattenedTestIds;
this.updateTestResultsToPending();
this.runnerResultAnalyzer = this.getAnalyzer();
}
public async run(launchConfiguration: DebugConfiguration, progressReporter?: IProgressReporter): Promise<Set<string>> {
public async run(launchConfiguration: DebugConfiguration, token: CancellationToken, progressReporter?: IProgressReporter): Promise<void> {
let data: string = '';
this.server.on('connection', (socket: Socket) => {
this.socket = socket;
@ -53,7 +40,7 @@ export abstract class BaseRunner implements ITestRunner {
data = data.concat(iconv.decode(buffer, launchConfiguration.encoding || 'utf8'));
const index: number = data.lastIndexOf(os.EOL);
if (index >= 0) {
this.testResultAnalyzer.analyzeData(data.substring(0, index + os.EOL.length));
this.runnerResultAnalyzer.analyzeData(data.substring(0, index + os.EOL.length));
data = data.substring(index + os.EOL.length);
}
});
@ -70,49 +57,49 @@ export abstract class BaseRunner implements ITestRunner {
// Run from integrated terminal will terminate the debug session immediately after launching,
// So we force to use internal console here to make sure the session is still under debugger's control.
launchConfiguration.console = 'internalConsole';
launchConfiguration.internalConsoleOptions = 'openOnSessionStart';
launchConfiguration.__progressId = progressReporter?.getId();
let debugSession: DebugSession | undefined;
this.disposables.push(debug.onDidStartDebugSession((session: DebugSession) => {
if (session.name === launchConfiguration.name) {
debugSession = session;
}
}));
const uri: Uri = Uri.parse(this.context.tests[0].location.uri);
logger.verbose(`Launching with the following launch configuration: '${JSON.stringify(launchConfiguration, null, 2)}'\n`);
return await debug.startDebugging(workspace.getWorkspaceFolder(uri), launchConfiguration).then(async (success: boolean) => {
if (token.isCancellationRequested || progressReporter?.isCancelled()) {
this.tearDown();
return;
}
return await debug.startDebugging(this.testContext.workspaceFolder, launchConfiguration).then(async (success: boolean) => {
if (!success) {
this.tearDown();
return this.testResultAnalyzer.tearDown();
return;
}
return await new Promise<Set<string>>((resolve: (ids: Set<string>) => void): void => {
token.onCancellationRequested(() => {
debugSession?.customRequest('disconnect', { restart: false });
});
return await new Promise<void>((resolve: () => void): void => {
this.disposables.push(
debug.onDidTerminateDebugSession((session: DebugSession): void => {
if (launchConfiguration.name === session.name) {
debugSession = undefined;
this.tearDown();
if (data.length > 0) {
this.testResultAnalyzer.analyzeData(data);
this.runnerResultAnalyzer.analyzeData(data);
}
return resolve(this.testResultAnalyzer.tearDown());
return resolve();
}
}),
);
});
}, ((reason: any): any => {
logger.error(`${reason}`);
}, ((): any => {
this.tearDown();
return this.testResultAnalyzer.tearDown();
return;
}));
}
public async tearDown(): Promise<void> {
for (const id of this.testIds) {
const result: ITestResult | undefined = testResultManager.getResultById(id);
// In case that unexpected errors terminate the execution
if (result && (result.status === TestStatus.Pending || result.status === TestStatus.Running)) {
result.status = undefined;
testResultManager.storeResult(result);
}
}
try {
if (this.socket) {
this.socket.removeAllListeners();
@ -128,31 +115,10 @@ export abstract class BaseRunner implements ITestRunner {
disposable.dispose();
}
} catch (error) {
logger.error('Failed to clean up', error);
sendError(error);
}
}
public get runnerJarFilePath(): Promise<string> {
return this.getPath('com.microsoft.java.test.runner.jar');
}
public get runnerLibPath(): Promise<string> {
return this.getPath('lib');
}
public get runnerMainClassName(): string {
return 'com.microsoft.java.test.runner.Launcher';
}
public get serverPort(): number {
const address: AddressInfo = this.server.address() as AddressInfo;
if (address) {
return address.port;
}
throw new Error('The socket server is not started yet.');
}
public getApplicationArgs(config?: IExecutionConfig): string[] {
const applicationArgs: string[] = [];
applicationArgs.push(`${(this.server.address() as AddressInfo).port}`);
@ -166,59 +132,19 @@ export abstract class BaseRunner implements ITestRunner {
return applicationArgs;
}
protected abstract get testResultAnalyzer(): BaseRunnerResultAnalyzer;
protected get runnerDir(): string {
return path.join(this.extensionPath, 'server');
protected async startSocketServer(): Promise<void> {
this.server = createServer();
const socketPort: number = await getPort();
await new Promise<void>((resolve: () => void): void => {
this.server.listen(socketPort, Configurations.LOCAL_HOST, resolve);
});
}
protected getRunnerCommandParams(_config?: IExecutionConfig): string[] {
return [];
}
protected async startSocketServer(): Promise<void> {
this.server = createServer();
const socketPort: number = await getPort();
await new Promise<void>((resolve: () => void): void => {
this.server.listen(socketPort, LOCAL_HOST, resolve);
});
}
private async getPath(subPath: string): Promise<string> {
const fullPath: string = path.join(this.runnerDir, subPath);
if (await fse.pathExists(fullPath)) {
return fullPath;
}
throw new Error(`Failed to find path: ${fullPath}`);
}
private updateTestResultsToPending(): void {
const runningResults: ITestResult[] = [];
for (const id of this.testIds) {
runningResults.push({
id,
status: TestStatus.Pending,
});
}
testResultManager.storeResult(...runningResults);
}
/**
* Add all of the method IDs into `flattenedItemIds`. No need to recursively call this method,
* since the input `test` is not tree-like
*/
private flattenTestIds(test: ITestItem, flattenedItemIds: string[]): void {
if (test.level === TestLevel.Method) {
flattenedItemIds.push(test.id);
} else if (test.level === TestLevel.Class) {
if (test.children) {
flattenedItemIds.push(...test.children);
} else {
// for JUnit 4's @Suite.SuiteClasses
flattenedItemIds.push(test.id);
}
}
}
protected abstract getAnalyzer(): IRunnerResultAnalyzer;
}
export interface IJUnitLaunchArguments {

Просмотреть файл

@ -1,66 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { logger } from '../../logger/logger';
import { testResultManager } from '../../testResultManager';
import { ITestOutputData, ITestResult, TestStatus } from '../models';
export abstract class BaseRunnerResultAnalyzer {
protected testIds: Set<string> = new Set<string>();
private readonly regex: RegExp = /@@<TestRunner-({[\s\S]*?})-TestRunner>/;
constructor(protected projectName: string) {
}
public analyzeData(data: string): void {
const lines: string[] = data.split(/\r?\n/);
for (const line of lines) {
if (!line) {
continue;
}
const match: RegExpExecArray | null = this.regex.exec(line);
if (match) {
// Message from Test Runner executable
try {
this.processData(match[1]);
} catch (error) {
logger.error(`Failed to parse output data: ${data}`, error);
}
} else {
// Message from the test case itself
logger.info(line);
}
}
}
public tearDown(): Set<string> {
for (const id of this.testIds) {
const result: ITestResult | undefined = testResultManager.getResultById(id);
// In case that unexpected errors terminate the execution
if (result && (!result.status || result.status === TestStatus.Pending || result.status === TestStatus.Running)) {
testResultManager.removeResultById(id);
this.testIds.delete(id);
}
}
return this.testIds;
}
protected processData(data: string): void {
const outputData: ITestOutputData = JSON.parse(data) as ITestOutputData;
if (outputData.name.toLocaleLowerCase() === 'error') {
logger.error(this.unescape(data));
} else {
// Append '\n' becuase the original line separator has been splitted
logger.verbose(this.unescape(data) + '\n');
}
}
protected unescape(content: string): string {
return content.replace(/\\r/gm, '\r')
.replace(/\\f/gm, '\f')
.replace(/\\n/gm, '\n')
.replace(/\\t/gm, '\t')
.replace(/\\b/gm, '\b')
.replace(/\\"/gm, '"');
}
}

Просмотреть файл

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export interface IRunnerResultAnalyzer {
analyzeData(data: string): void;
processData(data: string): void;
}

Просмотреть файл

@ -1,16 +1,48 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { logger } from '../../logger/logger';
import { testResultManager } from '../../testResultManager';
import { BaseRunnerResultAnalyzer } from '../baseRunner/BaseRunnerResultAnalyzer';
import { ITestResult, TestStatus } from '../models';
import * as path from 'path';
import { Location, MarkdownString, Range, TestItem, TestMessage, TestResultState } from 'vscode';
import { INVOCATION_PREFIX } from '../../constants';
import { dataCache, ITestItemData } from '../../controller/testItemDataCache';
import { createTestItem } from '../../controller/utils';
import { IJavaTestItem, IRunTestContext, TestKind, TestLevel } from '../../types';
import { IRunnerResultAnalyzer } from '../baseRunner/IRunnerResultAnalyzer';
import { findTestLocation, setTestState } from '../utils';
export class JUnitRunnerResultAnalyzer extends BaseRunnerResultAnalyzer {
export class JUnitRunnerResultAnalyzer implements IRunnerResultAnalyzer {
private currentTestItem: string;
private traces: string;
private isRecordingTraces: boolean;
private testOutputMapping: Map<string, ITestInfo> = new Map();
private triggeredTestsMapping: Map<string, TestItem> = new Map();
private currentTestState: TestResultState;
private currentItem: TestItem | undefined;
private currentDuration: number = 0;
private traces: MarkdownString;
private assertionFailure: TestMessage | undefined;
private recordingType: RecordingType;
private expectString: string;
private actualString: string;
private projectName: string;
private incompleteTestSuite: ITestInfo[] = [];
constructor(private testContext: IRunTestContext) {
this.projectName = testContext.projectName;
const queue: TestItem[] = [...testContext.testItems];
while (queue.length) {
const item: TestItem = queue.shift()!;
const testLevel: TestLevel | undefined = dataCache.get(item)?.testLevel;
if (testLevel === undefined || testLevel === TestLevel.Invocation) {
continue;
} else if (testLevel === TestLevel.Method && item.parent) {
this.triggeredTestsMapping.set(item.parent.id, item.parent);
} else {
item.children.forEach((child: TestItem) => {
queue.push(child);
});
}
this.triggeredTestsMapping.set(item.id, item);
}
}
public analyzeData(data: string): void {
const lines: string[] = data.split(/\r?\n/);
@ -19,100 +51,136 @@ export class JUnitRunnerResultAnalyzer extends BaseRunnerResultAnalyzer {
continue;
}
this.processData(line);
logger.verbose(line + '\n');
this.testContext.testRun.appendOutput(line + '\r\n');
}
}
protected processData(data: string): void {
if (data.startsWith(MessageId.TestStart)) {
const testId: string = this.getTestId(data);
if (!testId) {
public processData(data: string): void {
if (data.startsWith(MessageId.TestTree)) {
this.enlistToTestMapping(data.substr(MessageId.TestTree.length).trim());
} else if (data.startsWith(MessageId.TestStart)) {
const item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestStart.length));
if (!item) {
return;
}
this.currentTestItem = testId;
let result: ITestResult;
if (this.testIds.has(testId)) {
result = Object.assign({}, testResultManager.getResultById(testId), {
id: testId,
status: TestStatus.Running,
});
} else {
// the test has not been executed in current test session.
// create a new result object
result = {
id: testId,
status: TestStatus.Running,
};
this.testIds.add(testId);
if (item.id !== this.currentItem?.id) {
this.initializeCache(item);
}
this.testContext.testRun.started(item);
const start: number = Date.now();
if (data.indexOf(MessageId.IGNORE_TEST_PREFIX) > -1) {
result.status = TestStatus.Skip;
} else if (result.duration === undefined) {
result.duration = -start;
} else if (result.duration >= 0) {
if (this.currentDuration === 0) {
this.currentDuration = -start;
} else if (this.currentDuration > 0) {
// Some test cases may executed multiple times (@RepeatedTest), we need to calculate the time for each execution
result.duration -= start;
this.currentDuration -= start;
}
testResultManager.storeResult(result);
} else if (data.startsWith(MessageId.TestEnd)) {
const testId: string = this.getTestId(data);
if (testId) {
const finishedResult: ITestResult | undefined = testResultManager.getResultById(testId);
if (!finishedResult) {
return;
}
if (finishedResult.status === TestStatus.Running) {
if (finishedResult.trace) {
finishedResult.status = TestStatus.Fail;
} else {
finishedResult.status = TestStatus.Pass;
}
}
updateElapsedTime(finishedResult);
testResultManager.storeResult(finishedResult);
}
} else if (data.startsWith(MessageId.TestFailed) || data.startsWith(MessageId.TestError)) {
const testId: string = this.getTestId(data);
if (testId) {
this.currentTestItem = testId;
const failedResult: ITestResult = Object.assign({}, testResultManager.getResultById(testId), {
id: testId,
status: data.indexOf(MessageId.ASSUMPTION_FAILED_TEST_PREFIX) > -1 ? TestStatus.Skip : TestStatus.Fail,
});
updateElapsedTime(failedResult);
testResultManager.storeResult(failedResult);
this.testIds.add(testId);
}
} else if (data.startsWith(MessageId.TraceStart)) {
this.traces = '';
this.isRecordingTraces = true;
} else if (data.startsWith(MessageId.TraceEnd)) {
const failedResult: ITestResult | undefined = testResultManager.getResultById(this.currentTestItem);
if (!failedResult) {
if (!this.currentItem) {
return;
}
failedResult.trace = this.traces;
this.isRecordingTraces = false;
testResultManager.storeResult(failedResult);
} else if (this.isRecordingTraces) {
this.traces += data + '\n';
if (this.currentDuration < 0) {
const end: number = Date.now();
this.currentDuration += end;
}
if (data.indexOf(MessageId.IGNORE_TEST_PREFIX) > -1) {
this.currentTestState = TestResultState.Skipped;
} else if (this.currentTestState === TestResultState.Running) {
this.currentTestState = TestResultState.Passed;
}
setTestState(this.testContext.testRun, this.currentItem, this.currentTestState, undefined, this.currentDuration);
} else if (data.startsWith(MessageId.TestFailed)) {
if (data.indexOf(MessageId.ASSUMPTION_FAILED_TEST_PREFIX) > -1) {
this.currentTestState = TestResultState.Skipped;
} else {
this.currentTestState = TestResultState.Failed;
}
} else if (data.startsWith(MessageId.TestError)) {
const item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestError.length));
if (!item) {
return;
}
if (item.id !== this.currentItem?.id) {
this.initializeCache(item);
}
this.currentTestState = TestResultState.Errored;
} else if (data.startsWith(MessageId.TraceStart)) {
this.traces = new MarkdownString();
this.traces.isTrusted = true;
this.recordingType = RecordingType.StackTrace;
} else if (data.startsWith(MessageId.TraceEnd)) {
if (!this.currentItem) {
return;
}
const testMessage: TestMessage = new TestMessage(this.traces);
this.tryAppendMessage(this.currentItem, testMessage);
this.recordingType = RecordingType.None;
if (this.currentTestState === TestResultState.Errored) {
setTestState(this.testContext.testRun, this.currentItem, this.currentTestState);
}
} else if (data.startsWith(MessageId.ExpectStart)) {
this.recordingType = RecordingType.ExpectMessage;
} else if (data.startsWith(MessageId.ExpectEnd)) {
this.recordingType = RecordingType.None;
this.expectString = this.expectString.replace(/\n$/, '');
} else if (data.startsWith(MessageId.ActualStart)) {
this.recordingType = RecordingType.ActualMessage;
} else if (data.startsWith(MessageId.ActualEnd)) {
this.recordingType = RecordingType.None;
this.actualString = this.actualString.replace(/\n$/, '');
if (!this.assertionFailure && this.expectString && this.actualString) {
this.assertionFailure = TestMessage.diff(`Expected [${this.expectString}] but was [${this.actualString}]`, this.expectString, this.actualString);
}
} else if (this.recordingType === RecordingType.ExpectMessage) {
this.expectString += data + '\n';
} else if (this.recordingType === RecordingType.ActualMessage) {
this.actualString += data + '\n';
} else if (this.recordingType === RecordingType.StackTrace) {
if (!this.assertionFailure) {
const assertionRegExp: RegExp = /expected.*:.*<(.+?)>.*but.*:.*<(.+?)>/mi;
const assertionResults: RegExpExecArray | null = assertionRegExp.exec(data);
if (assertionResults && assertionResults.length === 3) {
this.assertionFailure = TestMessage.diff(`Expected [${assertionResults[1]}] but was [${assertionResults[2]}]`, assertionResults[1], assertionResults[2]);
}
}
const traceRegExp: RegExp = /(\s?at\s+)([\w$\\.]+\/)?((?:[\w$]+\.)+[<\w$>]+)\(([\w-$]+\.java):(\d+)\)/;
const traceResults: RegExpExecArray | null = traceRegExp.exec(data);
if (traceResults && traceResults.length === 6) {
this.traces.appendText(traceResults[1]);
this.traces.appendMarkdown(`${(traceResults[2] || '') + traceResults[3]}([${traceResults[4]}:${traceResults[5]}](command:_java.test.openStackTrace?${encodeURIComponent(JSON.stringify([data, this.projectName]))}))`);
if (this.assertionFailure && this.currentItem && path.basename(this.currentItem.uri?.fsPath || '') === traceResults[4]) {
const lineNum: number = parseInt(traceResults[5], 10);
if (this.currentItem.uri) {
this.assertionFailure.location = new Location(this.currentItem.uri, new Range(lineNum - 1, 0, lineNum, 0));
}
setTestState(this.testContext.testRun, this.currentItem, TestResultState.Failed, this.assertionFailure);
}
} else {
// in case the message contains message like: 'expected: <..> but was: <..>'
this.traces.appendText(data.replace(/</g, '&lt;').replace(/>/g, '&gt;'));
}
this.traces.appendText('\n');
}
}
protected getTestItem(message: string): TestItem | undefined {
const index: string = message.substring(0, message.indexOf(',')).trim();
return this.testOutputMapping.get(index)?.testItem;
}
protected getTestId(message: string): string {
/**
* The following regex expression is used to parse the test runner's output, which match the following components:
* '\d+,' - index from the test runner
* '(?:@AssumptionFailure: |@Ignore: )?' - indicate if the case is ignored due to assumption failure or disabled
* '(.*?)' - test method name
* '(?:\[\d+\])?' - execution index, it will appear for the JUnit4's parameterized test
* '(?:\[\d+.*?\])?' - execution index, it will appear for the JUnit4's parameterized test
* '\(([^)]*)\)[^(]*$' - class fully qualified name which wrapped by the last paired brackets, see:
* https://github.com/microsoft/vscode-java-test/issues/1075
*/
const regexp: RegExp = /\d+,(?:@AssumptionFailure: |@Ignore: )?(.*?)(?:\[\d+\])?\(([^)]*)\)[^(]*$/;
const regexp: RegExp = /(?:@AssumptionFailure: |@Ignore: )?(.*?)(?:\[\d+.*?\])?\(([^)]*)\)[^(]*$/;
const matchResults: RegExpExecArray | null = regexp.exec(message);
if (matchResults && matchResults.length === 3) {
return `${this.projectName}@${matchResults[2]}#${matchResults[1]}`;
@ -121,28 +189,160 @@ export class JUnitRunnerResultAnalyzer extends BaseRunnerResultAnalyzer {
// In case the output is class level, i.e.: `%ERROR 2,a.class.FullyQualifiedName`
const indexOfSpliter: number = message.lastIndexOf(',');
if (indexOfSpliter > -1) {
return `${this.projectName}@${message.slice(indexOfSpliter + 1)}#<TestError>`;
return `${this.projectName}@${message.slice(indexOfSpliter + 1)}`;
}
logger.error(`Failed to parse the message: ${message}`);
return '';
return `${this.projectName}@${message}`;
}
}
function updateElapsedTime(result: ITestResult): void {
if (result.duration && result.duration < 0) {
const end: number = Date.now();
result.duration += end;
protected initializeCache(item: TestItem): void {
this.currentTestState = TestResultState.Running;
this.currentItem = item;
this.currentDuration = 0;
this.assertionFailure = undefined;
this.expectString = '';
this.actualString = '';
this.recordingType = RecordingType.None;
}
private enlistToTestMapping(message: string): void {
const regExp: RegExp = /([^\\,]|\\\,?)+/gm;
// See MessageId.TestTree's comment for its format
const result: RegExpMatchArray | null = message.match(regExp);
if (result && result.length > 6) {
// for now, skip the param test for JUnit 4
if (/^\[\d+.*?\]$/.test(result[1])) {
return;
}
const index: string = result[0];
const testId: string = this.getTestId(result[1]);
const isSuite: boolean = result[2] === 'true';
const testCount: number = parseInt(result[3], 10);
const isDynamic: boolean = result[4] === 'true';
const parentIndex: string = result[5];
const displayName: string = result[6].replace(/\\,/g, ',');
let testItem: TestItem | undefined;
if (isDynamic) {
const parentInfo: ITestInfo | undefined = this.testOutputMapping.get(parentIndex);
const parent: TestItem | undefined = parentInfo?.testItem;
if (parent) {
const parentData: ITestItemData | undefined = dataCache.get(parent);
if (parentData?.testLevel === TestLevel.Method) {
testItem = createTestItem({
children: [],
uri: parent.uri?.toString(),
range: parent.range,
jdtHandler: parentData.jdtHandler,
fullName: parentData.fullName,
label: displayName,
id: `${INVOCATION_PREFIX}${parent.id}[#${parent.children.size + 1}]`,
projectName: parentData.projectName,
testKind: parentData.testKind,
testLevel: TestLevel.Invocation,
}, parent);
}
}
} else {
testItem = this.triggeredTestsMapping.get(testId);
if (this.incompleteTestSuite.length) {
const suiteIdx: number = this.incompleteTestSuite.length - 1;
const parentSuite: ITestInfo = this.incompleteTestSuite[suiteIdx];
parentSuite.testCount--;
if (parentSuite.testCount <= 0) {
this.incompleteTestSuite.pop();
}
if (!testItem && parentSuite.testItem) {
const itemData: IJavaTestItem | undefined = {
children: [],
uri: undefined,
range: undefined,
jdtHandler: '',
fullName: testId.substr(testId.indexOf('@') + 1),
label: displayName,
id: `${INVOCATION_PREFIX}${testId}`,
projectName: this.projectName,
testKind: this.testContext.kind,
testLevel: TestLevel.Invocation,
};
testItem = createTestItem(itemData, parentSuite.testItem);
}
}
if (isSuite && testCount > 0) {
this.incompleteTestSuite.push({
testId,
testCount,
testItem,
});
}
if (testItem && dataCache.get(testItem)?.testKind === TestKind.JUnit5 && testItem.label !== displayName) {
testItem.description = displayName;
}
}
this.testOutputMapping.set(index, {
testId,
testCount,
testItem,
});
}
}
private async tryAppendMessage(item: TestItem, testMessage: TestMessage): Promise<void> {
if (item.uri && item.range) {
testMessage.location = new Location(item.uri, item.range);
} else {
let id: string = item.id;
if (id.startsWith(INVOCATION_PREFIX)) {
id = id.substring(INVOCATION_PREFIX.length);
}
const location: Location | undefined = await findTestLocation(id);
testMessage.location = location;
}
setTestState(this.testContext.testRun, item, TestResultState.Failed, testMessage);
}
}
enum MessageId {
/**
* Notification about a test inside the test suite.
* TEST_TREE + testId + "," + testName + "," + isSuite + "," + testCount + "," + isDynamicTest +
* "," + parentId + "," + displayName + "," + parameterTypes + "," + uniqueId
* isSuite = "true" or "false"
* isDynamicTest = "true" or "false"
* parentId = the unique id of its parent if it is a dynamic test, otherwise can be "-1"
* displayName = the display name of the test
* parameterTypes = comma-separated list of method parameter types if applicable, otherwise an
* empty string
* uniqueId = the unique ID of the test provided by JUnit launcher, otherwise an empty string
*/
TestTree = '%TSTTREE',
TestStart = '%TESTS',
TestEnd = '%TESTE',
TestFailed = '%FAILED',
TestError = '%ERROR',
ExpectStart = '%EXPECTS',
ExpectEnd = '%EXPECTE',
ActualStart = '%ACTUALS',
ActualEnd = '%ACTUALE',
TraceStart = '%TRACES',
TraceEnd = '%TRACEE',
IGNORE_TEST_PREFIX = '@Ignore: ',
ASSUMPTION_FAILED_TEST_PREFIX = '@AssumptionFailure: ',
}
interface ITestInfo {
testId: string;
testCount: number;
testItem: TestItem | undefined;
}
enum RecordingType {
None,
StackTrace,
ExpectMessage,
ActualMessage,
}

Просмотреть файл

@ -2,15 +2,14 @@
// Licensed under the MIT license.
import { AddressInfo } from 'net';
import { DebugConfiguration } from 'vscode';
import { CancellationToken, DebugConfiguration } from 'vscode';
import { IProgressReporter } from '../../debugger.api';
import { BaseRunner } from '../baseRunner/BaseRunner';
import { BaseRunnerResultAnalyzer } from '../baseRunner/BaseRunnerResultAnalyzer';
import { IRunnerResultAnalyzer } from '../baseRunner/IRunnerResultAnalyzer';
import { JUnitRunnerResultAnalyzer } from './JUnitRunnerResultAnalyzer';
export class JUnitRunner extends BaseRunner {
public async run(launchConfiguration: DebugConfiguration, progressReporter?: IProgressReporter): Promise<Set<string>> {
public async run(launchConfiguration: DebugConfiguration, token: CancellationToken, progressReporter?: IProgressReporter): Promise<void> {
if (launchConfiguration.args) {
// We need to replace the socket port number since the socket is established from the client side.
// The port number returned from the server side is a fake one.
@ -22,13 +21,11 @@ export class JUnitRunner extends BaseRunner {
args.push('-port', `${(this.server.address() as AddressInfo).port}`);
}
}
return super.run(launchConfiguration, progressReporter);
return super.run(launchConfiguration, token, progressReporter);
}
protected get testResultAnalyzer(): BaseRunnerResultAnalyzer {
if (!this.runnerResultAnalyzer) {
this.runnerResultAnalyzer = new JUnitRunnerResultAnalyzer(this.context.projectName);
}
return this.runnerResultAnalyzer;
protected getAnalyzer(): IRunnerResultAnalyzer {
return new JUnitRunnerResultAnalyzer(this.testContext);
}
}

Просмотреть файл

@ -1,42 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ITestItem, TestKind, TestLevel } from '../protocols';
export interface ITestResult {
id: string;
status?: TestStatus;
trace?: string;
message?: string;
duration?: number;
summary?: string;
}
export enum TestStatus {
Pending = 'Pending',
Running = 'Running',
Pass = 'Pass',
Fail = 'Fail',
Skip = 'Skip',
}
export interface ITestOutputData {
type: TestOutputType;
name: string;
}
export enum TestOutputType {
Info,
Error,
}
export interface IRunnerContext {
scope: TestLevel;
testUri: string;
fullName: string;
projectName: string;
isDebug: boolean;
kind: TestKind;
tests: ITestItem[];
isHierarchicalPackage?: boolean;
}

Просмотреть файл

@ -1,217 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as _ from 'lodash';
import { DebugConfiguration, ExtensionContext, ProgressLocation, Uri, window, workspace, WorkspaceFolder } from 'vscode';
import { testCodeLensController } from '../codelens/TestCodeLensController';
import { ReportShowSetting } from '../constants/configs';
import { IProgressReporter } from '../debugger.api';
import { progressProvider } from '../extension';
import { logger } from '../logger/logger';
import { ITestItem, TestKind } from '../protocols';
import { IExecutionConfig } from '../runConfigs';
import { testReportProvider } from '../testReportProvider';
import { testResultManager } from '../testResultManager';
import { testStatusBarProvider } from '../testStatusBarProvider';
import { loadRunConfig } from '../utils/configUtils';
import { resolveLaunchConfigurationForRunner } from '../utils/launchUtils';
import { getShowReportSetting } from '../utils/settingUtils';
import * as uiUtils from '../utils/uiUtils';
import { BaseRunner } from './baseRunner/BaseRunner';
import { JUnitRunner } from './junitRunner/JunitRunner';
import { IRunnerContext, ITestResult, TestStatus } from './models';
import { TestNGRunner } from './testngRunner/TestNGRunner';
class RunnerScheduler {
private _context: ExtensionContext;
private _isRunning: boolean;
private _runnerMap: Map<BaseRunner, ITestItem[]> | undefined;
private _executionCache: IExecutionCache | undefined;
public initialize(context: ExtensionContext): void {
this._context = context;
}
public async relaunch(): Promise<void> {
if (!this._executionCache || !this._executionCache.context) {
logger.info('No test history available, please run some test cases first to relaunch the tests.\n');
return;
}
await this.run(this._executionCache.context);
}
public async run(runnerContext: IRunnerContext, progressReporter?: IProgressReporter, launchConfiguration?: DebugConfiguration): Promise<void> {
if (this._isRunning) {
window.showInformationMessage('A test session is currently running. Please wait until it finishes.\n');
return;
}
this._isRunning = true;
progressReporter = progressReporter || progressProvider?.createProgressReporter(runnerContext.isDebug ? 'Debug Test' : 'Run Test');
this._executionCache = {
context: _.cloneDeep(runnerContext),
};
let allIds: Set<string> = new Set<string>();
try {
this._runnerMap = this.classifyTestsByKind(runnerContext.tests);
for (const [runner, tests] of this._runnerMap.entries()) {
runnerContext.kind = tests[0].kind;
runnerContext.projectName = tests[0].project;
runnerContext.tests = tests;
await runner.setup(runnerContext);
let resolvedConfiguration: DebugConfiguration | undefined = launchConfiguration;
if (!resolvedConfiguration) {
// The test items that belong to a test runner, here the test items should be in the same workspace folder.
const workspaceFolder: WorkspaceFolder | undefined = workspace.getWorkspaceFolder(Uri.parse(tests[0].location.uri));
const config: IExecutionConfig | undefined = await loadRunConfig(workspaceFolder);
if (!config) {
logger.info('Test job is canceled.\n');
continue;
}
if (progressReporter?.isCancelled()) {
progressReporter = progressProvider?.createProgressReporter(runnerContext.isDebug ? 'Debug Test' : 'Run Test', ProgressLocation.Notification, true);
}
progressReporter?.report('Resolving launch configuration...');
resolvedConfiguration = await resolveLaunchConfigurationForRunner(runner, runnerContext, config);
}
const ids: Set<string> = await runner.run(resolvedConfiguration, progressReporter);
allIds = new Set([...allIds, ...ids]);
}
const finalResults: ITestResult[] = testResultManager.getResultsByIds(Array.from(allIds));
testStatusBarProvider.showTestResult(finalResults);
testCodeLensController.refresh();
this.showReportIfNeeded(finalResults);
this._executionCache.results = finalResults;
} catch (error) {
logger.error(error.toString());
uiUtils.showError(error);
} finally {
progressReporter?.done();
await this.cleanUp(false);
}
}
public getExecutionCache(): IExecutionCache | undefined {
return this._executionCache;
}
public async cleanUp(isCancel: boolean): Promise<void> {
try {
const promises: Array<Promise<void>> = [];
if (this._runnerMap) {
for (const runner of this._runnerMap.keys()) {
promises.push(runner.tearDown());
}
this._runnerMap.clear();
this._runnerMap = undefined;
}
await Promise.all(promises);
if (isCancel) {
logger.info('Test job is canceled.\n');
}
} catch (error) {
logger.error('Failed to clean up', error);
}
this._isRunning = false;
}
private classifyTestsByKind(tests: ITestItem[]): Map<BaseRunner, ITestItem[]> {
const testMap: Map<string, ITestItem[]> = this.mapTestsByProjectAndKind(tests);
return this.mapTestsByRunner(testMap);
}
private mapTestsByProjectAndKind(tests: ITestItem[]): Map<string, ITestItem[]> {
const map: Map<string, ITestItem[]> = new Map<string, ITestItem[]>();
// Store all the covered test items, e.g. if a class will be run, all the child method will be added into it
const coveredSet: Set<string> = new Set<string>();
for (const test of tests) {
if (coveredSet.has(test.id)) {
continue;
}
if (!(test.kind in TestKind)) {
logger.error(`Unknown kind of test item: ${test.fullName}`);
continue;
}
const key: string = `${test.project}/${test.kind}`;
const testArray: ITestItem[] | undefined = map.get(key);
if (testArray) {
testArray.push(test);
} else {
map.set(key, [test]);
}
coveredSet.add(test.id);
if (test.children) {
for (const childId of test.children) {
coveredSet.add(childId);
}
}
}
return map;
}
private mapTestsByRunner(testsPerProjectAndKind: Map<string, ITestItem[]>): Map<BaseRunner, ITestItem[]> {
const map: Map<BaseRunner, ITestItem[]> = new Map<BaseRunner, ITestItem[]>();
for (const tests of testsPerProjectAndKind.values()) {
const runner: BaseRunner | undefined = this.getRunnerByKind(tests[0].kind);
if (runner) {
map.set(runner, tests);
} else {
window.showWarningMessage(`Cannot find matched runner to run the test: ${tests[0].kind}`);
}
}
return map;
}
private getRunnerByKind(kind: TestKind): BaseRunner | undefined {
switch (kind) {
case TestKind.JUnit:
case TestKind.JUnit5:
return new JUnitRunner(this._context.extensionPath);
case TestKind.TestNG:
return new TestNGRunner(this._context.extensionPath);
default:
return undefined;
}
}
private showReportIfNeeded(finalResults: ITestResult[]): void {
if (finalResults.length === 0) {
return;
}
const showSetting: string = getShowReportSetting();
switch (showSetting) {
case ReportShowSetting.Always:
testReportProvider.report(finalResults);
break;
case ReportShowSetting.OnFail:
const hasFailedTests: boolean = finalResults.some((result: ITestResult) => {
return result.status === TestStatus.Fail;
});
if (hasFailedTests) {
testReportProvider.report(finalResults);
} else {
testReportProvider.update(finalResults);
}
break;
case ReportShowSetting.Never:
testReportProvider.update(finalResults);
break;
default:
break;
}
}
}
export interface IExecutionCache {
context: IRunnerContext;
results?: ITestResult[];
}
export const runnerScheduler: RunnerScheduler = new RunnerScheduler();

Просмотреть файл

@ -1,29 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { logger } from '../../logger/logger';
import { TestItem } from 'vscode';
import { dataCache } from '../../controller/testItemDataCache';
import { TestLevel } from '../../types';
import { BaseRunner } from '../baseRunner/BaseRunner';
import { BaseRunnerResultAnalyzer } from '../baseRunner/BaseRunnerResultAnalyzer';
import { IRunnerResultAnalyzer } from '../baseRunner/IRunnerResultAnalyzer';
import { TestNGRunnerResultAnalyzer } from './TestNGRunnerResultAnalyzer';
export class TestNGRunner extends BaseRunner {
public getRunnerCommandParams(): string[] {
return ['testng', ...this.testIds.map((id: string) => {
const testMethods: TestItem[] = [];
const queue: TestItem[] = [...this.testContext.testItems];
while (queue.length) {
const item: TestItem = queue.shift()!;
const testLevel: TestLevel | undefined = dataCache.get(item)?.testLevel;
if (testLevel === undefined) {
continue;
}
if (testLevel === TestLevel.Method) {
testMethods.push(item);
} else {
item.children.forEach((child: TestItem) => {
queue.push(child);
});
}
}
return ['testng', ...testMethods.map((method: TestItem) => {
// parse to fullName
const index: number = id.indexOf('@');
const index: number = method.id.indexOf('@');
if (index < 0) {
logger.error(`Invalid ID: ${id}`);
return '';
}
return id.slice(index + 1);
return method.id.slice(index + 1);
}).filter(Boolean)];
}
protected get testResultAnalyzer(): BaseRunnerResultAnalyzer {
if (!this.runnerResultAnalyzer) {
this.runnerResultAnalyzer = new TestNGRunnerResultAnalyzer(this.context.projectName);
}
return this.runnerResultAnalyzer;
protected getAnalyzer(): IRunnerResultAnalyzer {
return new TestNGRunnerResultAnalyzer(this.testContext);
}
}

Просмотреть файл

@ -1,55 +1,167 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { testResultManager } from '../../testResultManager';
import { BaseRunnerResultAnalyzer } from '../baseRunner/BaseRunnerResultAnalyzer';
import { ITestOutputData, ITestResult, TestStatus } from '../models';
import { Location, MarkdownString, TestItem, TestMessage, TestResultState } from 'vscode';
import { dataCache } from '../../controller/testItemDataCache';
import { IRunTestContext, TestLevel } from '../../types';
import { IRunnerResultAnalyzer } from '../baseRunner/IRunnerResultAnalyzer';
import { setTestState } from '../utils';
const TEST_START: string = 'testStarted';
const TEST_FAIL: string = 'testFailed';
const TEST_FINISH: string = 'testFinished';
export class TestNGRunnerResultAnalyzer extends BaseRunnerResultAnalyzer {
export class TestNGRunnerResultAnalyzer implements IRunnerResultAnalyzer {
protected processData(data: string): void {
super.processData(data);
const outputData: ITestNGOutputData = JSON.parse(data) as ITestNGOutputData;
const id: string = `${this.projectName}@${outputData.attributes.name}`;
switch (outputData.name) {
case TEST_START:
testResultManager.storeResult({
id,
status: TestStatus.Running,
private readonly regex: RegExp = /@@<TestRunner-({[\s\S]*?})-TestRunner>/;
private triggeredTestsMapping: Map<string, TestItem> = new Map();
private currentTestState: TestResultState;
private currentItem: TestItem | undefined;
private projectName: string;
constructor(private testContext: IRunTestContext) {
this.projectName = testContext.projectName;
const queue: TestItem[] = [...testContext.testItems];
while (queue.length) {
const item: TestItem = queue.shift()!;
const testLevel: TestLevel | undefined = dataCache.get(item)?.testLevel;
if (testLevel === undefined) {
continue;
}
if (testLevel === TestLevel.Method) {
this.triggeredTestsMapping.set(item.id, item);
} else {
item.children.forEach((child: TestItem) => {
queue.push(child);
});
this.testIds.add(id);
break;
case TEST_FAIL:
const failedResult: ITestResult | undefined = testResultManager.getResultById(id);
if (!failedResult) {
return;
}
failedResult.status = TestStatus.Fail;
failedResult.message = outputData.attributes.message;
failedResult.trace = outputData.attributes.trace;
testResultManager.storeResult(failedResult);
break;
case TEST_FINISH:
const finishedResult: ITestResult | undefined = testResultManager.getResultById(id);
if (!finishedResult) {
return;
}
if (finishedResult.status === TestStatus.Running) {
finishedResult.status = TestStatus.Pass;
}
finishedResult.duration = Number.parseInt(outputData.attributes.duration, 10);
testResultManager.storeResult(finishedResult);
break;
}
}
}
public analyzeData(data: string): void {
const lines: string[] = data.split(/\r?\n/);
for (const line of lines) {
if (!line) {
continue;
}
const match: RegExpExecArray | null = this.regex.exec(line);
if (match) {
// Message from Test Runner executable
try {
this.processData(match[1]);
} catch (error) {
this.testContext.testRun.appendOutput(`[ERROR] Failed to parse output data: ${line}\n`);
}
} else {
this.testContext.testRun.appendOutput(line + '\r\n');
}
}
}
public processData(data: string): void {
const outputData: ITestNGOutputData = JSON.parse(data) as ITestNGOutputData;
if (outputData.name.toLocaleLowerCase() === 'error') {
this.testContext.testRun.appendOutput(`[ERROR] ${this.unescape(data)}\r\n`);
} else {
this.testContext.testRun.appendOutput(`${this.unescape(data)}\r\n`);
}
const id: string = `${this.projectName}@${outputData.attributes.name}`;
if (outputData.name === TEST_START) {
this.initializeCache();
const item: TestItem | undefined = this.getTestItem(id);
if (!item) {
return;
}
this.currentTestState = TestResultState.Running;
this.testContext.testRun.started(item);
} else if (outputData.name === TEST_FAIL) {
const item: TestItem | undefined = this.getTestItem(id);
if (!item) {
return;
}
this.currentTestState = TestResultState.Failed;
const testMessages: TestMessage[] = [];
if (outputData.attributes.message) {
const message: TestMessage = new TestMessage(outputData.attributes.message.trim());
if (item.uri && item.range) {
message.location = new Location(item.uri, item.range);
}
testMessages.push(message);
}
if (outputData.attributes.trace) {
const traceString: string = outputData.attributes.trace.trim();
const markdownTrace: MarkdownString = new MarkdownString();
markdownTrace.isTrusted = true;
const traceRegExp: RegExp = /(\s?at\s+)([\w$\\.]+\/)?((?:[\w$]+\.)+[<\w$>]+)\(([\w-$]+\.java):(\d+)\)/;
for (const line of traceString.split(/\r?\n/)) {
const traceResults: RegExpExecArray | null = traceRegExp.exec(line);
if (traceResults && traceResults.length === 6) {
markdownTrace.appendText(traceResults[1]);
markdownTrace.appendMarkdown(`${(traceResults[2] || '') + traceResults[3]}([${traceResults[4]}:${traceResults[5]}](command:_java.test.openStackTrace?${encodeURIComponent(JSON.stringify([data, this.projectName]))}))`);
} else {
// in case the message contains message like: 'expected: <..> but was: <..>'
markdownTrace.appendText(line.replace(/</g, '&lt;').replace(/>/g, '&gt;'));
}
markdownTrace.appendText('\n');
}
const testMessage: TestMessage = new TestMessage(markdownTrace);
if (item.uri && item.range) {
testMessage.location = new Location(item.uri, item.range);
}
testMessages.push(testMessage);
}
const duration: number = Number.parseInt(outputData.attributes.duration, 10);
setTestState(this.testContext.testRun, item, this.currentTestState, testMessages, duration);
} else if (outputData.name === TEST_FINISH) {
const item: TestItem | undefined = this.getTestItem(data);
if (!item) {
return;
}
if (this.currentTestState === TestResultState.Running) {
this.currentTestState = TestResultState.Passed;
}
const duration: number = Number.parseInt(outputData.attributes.duration, 10);
setTestState(this.testContext.testRun, item, this.currentTestState, undefined, duration);
}
}
protected getTestItem(testId: string): TestItem | undefined {
if (this.currentItem) {
return this.currentItem;
}
this.currentItem = this.triggeredTestsMapping.get(testId);
return this.currentItem;
}
protected unescape(content: string): string {
return content.replace(/\\r/gm, '\r')
.replace(/\\f/gm, '\f')
.replace(/\\n/gm, '\n')
.replace(/\\t/gm, '\t')
.replace(/\\b/gm, '\b')
.replace(/\\"/gm, '"');
}
protected initializeCache(): void {
this.currentTestState = TestResultState.Queued;
this.currentItem = undefined;
}
}
interface ITestNGOutputData extends ITestOutputData {
interface ITestNGOutputData {
attributes: ITestNGAttributes;
type: TestOutputType;
name: string;
}
enum TestOutputType {
Info,
Error,
}
interface ITestNGAttributes {

38
src/runners/utils.ts Normal file
Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Location, TestItem, TestMessage, TestResultState, TestRun, Uri } from 'vscode';
import { JavaTestRunnerCommands } from '../constants';
import { asRange } from '../controller/utils';
import { executeJavaLanguageServerCommand } from '../utils/commandUtils';
export async function findTestLocation(fullName: string): Promise<Location | undefined> {
const location: any | undefined = await executeJavaLanguageServerCommand<any>(
JavaTestRunnerCommands.FIND_TEST_LOCATION, fullName);
if (location) {
return new Location(Uri.parse(location.uri), asRange(location.range)!);
}
return undefined;
}
export function setTestState(testRun: TestRun, item: TestItem, result: TestResultState, message?: TestMessage | TestMessage[], duration?: number): void {
switch (result) {
case TestResultState.Errored:
testRun.errored(item, message || [], duration);
break;
case TestResultState.Failed:
testRun.failed(item, message || [], duration);
break;
case TestResultState.Passed:
testRun.passed(item, duration);
break;
case TestResultState.Skipped:
testRun.skipped(item);
break;
case TestResultState.Running:
testRun.started(item);
default:
break;
}
}

Просмотреть файл

@ -1,80 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as _ from 'lodash';
import { Disposable, FileSystemWatcher, RelativePattern, Uri, workspace } from 'vscode';
import { testSourceProvider } from '../extension.bundle';
import { testExplorer } from './explorer/testExplorer';
import { isStandardServerReady } from './extension';
import { logger } from './logger/logger';
import { ITestItem, TestLevel } from './protocols';
import { testItemModel } from './testItemModel';
import { testResultManager } from './testResultManager';
class TestFileWatcher implements Disposable {
private disposables: Disposable[] = [];
private registerListenersDebounce: _.DebouncedFunc<() => Promise<void>> = _.debounce(this.registerListenersInternal, 2 * 1000 /*ms*/);
public async registerListeners(debounce: boolean = false): Promise<void> {
if (debounce) {
await this.registerListenersDebounce();
} else {
await this.registerListenersInternal();
}
}
public dispose(): void {
for (const disposable of this.disposables) {
if (disposable) {
disposable.dispose();
}
}
this.disposables = [];
}
protected async registerListenersInternal(): Promise<void> {
if (!isStandardServerReady()) {
return;
}
this.dispose();
try {
const patterns: RelativePattern[] = await testSourceProvider.getTestSourcePattern();
for (const pattern of patterns) {
const watcher: FileSystemWatcher = workspace.createFileSystemWatcher(pattern, true /* ignoreCreateEvents */);
this.registerWatcherListeners(watcher);
this.disposables.push(watcher);
}
} catch (error) {
logger.error('Failed to get the test paths', error);
const watcher: FileSystemWatcher = workspace.createFileSystemWatcher('**/*.java');
this.registerWatcherListeners(watcher);
this.disposables.push(watcher);
}
}
private registerWatcherListeners(watcher: FileSystemWatcher): void {
this.disposables.push(
watcher.onDidChange((uri: Uri) => {
const nodes: ITestItem[] = testItemModel.getItemsByFsPath(uri.fsPath);
for (const node of nodes) {
if (node.level === TestLevel.Class) {
testExplorer.refresh(node);
}
}
}),
watcher.onDidDelete((uri: Uri) => {
const nodes: ITestItem[] = testItemModel.getItemsByFsPath(uri.fsPath);
for (const node of nodes) {
testItemModel.removeTestItemById(node.id);
testResultManager.removeResultById(node.id);
}
testItemModel.removeIdMappingByFsPath(uri.fsPath);
testExplorer.refresh();
}),
);
}
}
export const testFileWatcher: TestFileWatcher = new TestFileWatcher();

Просмотреть файл

@ -1,106 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { CancellationToken, Disposable, Uri } from 'vscode';
import { ISearchTestItemParams, ITestItem, TestLevel } from './protocols';
import { searchTestCodeLens, searchTestItems, searchTestItemsAll } from './utils/commandUtils';
import { constructSearchTestItemParams } from './utils/protocolUtils';
class TestItemModel implements Disposable {
private store: Map<string, ITestItem> = new Map<string, ITestItem>();
private idMappedByFsPath: Map<string, Set<string>> = new Map<string, Set<string>>();
public getItemById(id: string): ITestItem | undefined {
return this.store.get(id);
}
public async getNodeChildren(parent: ITestItem): Promise<ITestItem[]> {
const searchParams: ISearchTestItemParams = constructSearchTestItemParams(parent.level, parent.fullName, parent.location.uri);
const childrenNodes: ITestItem[] = await searchTestItems(searchParams);
parent.children = childrenNodes.map((child: ITestItem) => child.id);
this.save([parent]);
return this.save(childrenNodes);
}
public async getAllNodes(level: TestLevel, fullName: string, uri: string, isHierarchicalPackage: boolean | undefined, token: CancellationToken): Promise<ITestItem[]> {
const searchParam: ISearchTestItemParams = constructSearchTestItemParams(level, fullName, uri);
const tests: ITestItem[] = await searchTestItemsAll({
...searchParam,
isHierarchicalPackage,
}, token);
if (token.isCancellationRequested) {
return [];
}
return this.save(tests);
}
public async getItemsForCodeLens(uri: Uri, token?: CancellationToken): Promise<ITestItem[]> {
const result: ITestItem[] = await searchTestCodeLens(uri.toString(), token);
return this.save(result);
}
public getItemsByFsPath(fsPath: string): ITestItem[] {
const res: ITestItem[] = [];
const idSet: Set<string> | undefined = this.idMappedByFsPath.get(fsPath);
if (idSet) {
for (const id of idSet) {
const item: ITestItem | undefined = this.store.get(id);
if (!item) {
continue;
}
res.push(item);
}
}
return res;
}
public removeTestItemById(id: string): boolean {
return this.store.delete(id);
}
public removeIdMappingByFsPath(fsPath: string): boolean {
return this.idMappedByFsPath.delete(fsPath);
}
public dispose(): void {
this.store.clear();
this.idMappedByFsPath.clear();
}
private save(items: ITestItem[]): ITestItem[] {
const storedItems: ITestItem[] = [];
for (const item of items) {
if (item.level < TestLevel.Class) {
// Only save class and method since they have the test results,
// still push the items into the returned array to let explorer show them.
storedItems.push(item);
continue;
}
let storedItem: ITestItem | undefined = this.store.get(item.id);
if (storedItem) {
storedItem = Object.assign(storedItem, item);
} else {
storedItem = Object.assign({}, item);
}
this.store.set(item.id, storedItem);
this.updateIdMapping(item);
storedItems.push(storedItem);
}
return storedItems;
}
private updateIdMapping(item: ITestItem): void {
const fsPath: string = Uri.parse(item.location.uri).fsPath;
const testSet: Set<string> | undefined = this.idMappedByFsPath.get(fsPath);
if (testSet) {
testSet.add(item.id);
} else {
this.idMappedByFsPath.set(fsPath, new Set([item.id]));
}
}
}
export const testItemModel: TestItemModel = new TestItemModel();

Просмотреть файл

@ -1,175 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as compareVersions from 'compare-versions';
import * as path from 'path';
import * as pug from 'pug';
import { commands, Disposable, ExtensionContext, Uri, ViewColumn, Webview, WebviewPanel, window } from 'vscode';
import { JavaTestRunnerCommands } from './constants/commands';
import { ILocation, ITestItem } from './protocols';
import { ITestResult, TestStatus } from './runners/models';
import { IExecutionCache, runnerScheduler } from './runners/runnerScheduler';
import { testItemModel } from './testItemModel';
import { getReportPosition } from './utils/settingUtils';
class TestReportProvider implements Disposable {
private compiledReportTemplate: pug.compileTemplate;
private context: ExtensionContext;
private panel: WebviewPanel | undefined;
private resourceBasePath: string;
private canResolveStackTrace: boolean;
public initialize(context: ExtensionContext, extensionVersion: string): void {
this.context = context;
this.compiledReportTemplate = require('pug-loader!../resources/templates/report.pug');
this.resourceBasePath = path.join(this.context.extensionPath, 'resources', 'templates');
if (compareVersions(extensionVersion, '0.70.0') >= 0) {
this.canResolveStackTrace = true;
}
}
public async report(tests?: ITestResult[]): Promise<void> {
const executionCache: IExecutionCache | undefined = runnerScheduler.getExecutionCache();
if (!tests && executionCache && executionCache.results) {
tests = executionCache.results;
}
if (!tests || tests.length === 0) {
return;
}
const position: ViewColumn = getReportPosition();
if (!this.panel) {
this.panel = window.createWebviewPanel('testRunnerReport', 'Java Test Report', position, {
localResourceRoots: [
Uri.file(this.resourceBasePath),
],
enableScripts: true,
retainContextWhenHidden: true,
enableFindWidget: true,
});
this.panel.onDidDispose(() => {
this.panel = undefined;
}, null, this.context.subscriptions);
this.panel.webview.onDidReceiveMessage(async (message: any) => {
if (!message) {
return;
}
switch (message.command) {
case JavaTestRunnerCommands.OPEN_DOCUMENT:
commands.executeCommand(JavaTestRunnerCommands.JAVA_TEST_REPORT_OPEN_TEST_SOURCE_LOCATION, message.uri, message.range, message.fullName);
break;
case JavaTestRunnerCommands.RELAUNCH_TESTS:
commands.executeCommand(JavaTestRunnerCommands.RELAUNCH_TESTS);
break;
case JavaTestRunnerCommands.JAVA_TEST_REPORT_OPEN_STACKTRACE:
commands.executeCommand(JavaTestRunnerCommands.JAVA_TEST_REPORT_OPEN_STACKTRACE, message.trace, message.fullName);
break;
default:
return;
}
}, null, this.context.subscriptions);
}
this.panel.webview.html = await testReportProvider.provideHtmlContent(tests, this.panel.webview);
this.panel.iconPath = {
light: Uri.file(path.join(this.resourceBasePath, '..', 'logo.lowers.light.svg')),
dark: Uri.file(path.join(this.resourceBasePath, '..', 'logo.lowers.dark.svg')),
};
this.panel.reveal(this.panel.viewColumn || position);
}
public async update(tests: ITestResult[]): Promise<void> {
if (this.panel) {
this.panel.webview.html = await testReportProvider.provideHtmlContent(tests, this.panel.webview);
}
}
public async provideHtmlContent(testResults: ITestResult[], webview: Webview): Promise<string> {
const allResultsMap: Map<string, ITestReportItem[]> = new Map();
const passedResultMap: Map<string, ITestReportItem[]> = new Map();
const failedResultMap: Map<string, ITestReportItem[]> = new Map();
const skippedResultMap: Map<string, ITestReportItem[]> = new Map();
let passedCount: number = 0;
let failedCount: number = 0;
let skippedCount: number = 0;
for (const result of testResults) {
if (result) {
const testItem: ITestItem | undefined = testItemModel.getItemById(result.id);
const reportItem: ITestReportItem = Object.assign({},
result,
{
fullName: result.id,
location: testItem ? testItem.location : undefined,
displayName: testItem ? testItem.displayName : result.id.slice(result.id.indexOf('#') + 1),
},
);
const classFullName: string = result.id.slice(result.id.indexOf('@') + 1, result.id.indexOf('#'));
this.putMethodResultIntoMap(allResultsMap, reportItem, classFullName);
switch (result.status) {
case TestStatus.Pass:
this.putMethodResultIntoMap(passedResultMap, reportItem, classFullName);
passedCount++;
break;
case TestStatus.Fail:
this.putMethodResultIntoMap(failedResultMap, reportItem, classFullName);
failedCount++;
break;
case TestStatus.Skip:
this.putMethodResultIntoMap(skippedResultMap, reportItem, classFullName);
skippedCount++;
break;
}
}
}
return this.compiledReportTemplate({
tests: allResultsMap,
passedTests: passedResultMap,
failedTests: failedResultMap,
skippedTests: skippedResultMap,
allCount: testResults.length,
passedCount,
failedCount,
skippedCount,
resourceBaseUri: webview.asWebviewUri(Uri.file(path.join(this.resourceBasePath))),
nonce: this.getNonce(),
canResolveStackTrace: this.canResolveStackTrace,
});
}
public dispose(): void {
if (this.panel) {
this.panel.dispose();
}
}
private putMethodResultIntoMap(map: Map<string, ITestReportItem[]>, reportItem: ITestReportItem, classFullName: string): void {
const methods: ITestReportItem[] | undefined = map.get(classFullName);
if (methods) {
methods.push(reportItem);
} else {
map.set(classFullName, [reportItem]);
}
}
private getNonce(): string {
let text: string = '';
const possible: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i: number = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
}
interface ITestReportItem extends ITestResult {
fullName: string;
location: ILocation | undefined;
displayName: string;
}
export const testReportProvider: TestReportProvider = new TestReportProvider();

Просмотреть файл

@ -1,53 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Disposable } from 'vscode';
import { testExplorer } from './explorer/testExplorer';
import { ITestItem, TestLevel } from './protocols';
import { ITestResult } from './runners/models';
import { testItemModel } from './testItemModel';
class TestResultManager implements Disposable {
private testResultMap: Map<string, ITestResult> = new Map<string, ITestResult>();
public async storeResult(...results: ITestResult[]): Promise<void> {
for (const result of results) {
this.testResultMap.set(result.id, result);
this.notifyExplorer(result.id);
}
}
public getResultById(testId: string): ITestResult | undefined {
return this.testResultMap.get(testId);
}
public getResultsByIds(testIds: string[]): ITestResult[] {
const results: ITestResult[] = [];
for (const id of testIds) {
const storedResult: ITestResult | undefined = this.testResultMap.get(id);
if (storedResult) {
results.push(storedResult);
}
}
return results;
}
public removeResultById(testId: string): void {
if (this.testResultMap.delete(testId)) {
this.notifyExplorer(testId);
}
}
public dispose(): void {
this.testResultMap.clear();
}
private notifyExplorer(id: string): void {
const item: ITestItem | undefined = testItemModel.getItemById(id);
if (item && item.level === TestLevel.Method) {
testExplorer.refresh(item);
}
}
}
export const testResultManager: TestResultManager = new TestResultManager();

Просмотреть файл

@ -1,67 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Disposable, StatusBarAlignment, StatusBarItem, window } from 'vscode';
import { JavaTestRunnerCommands } from './constants/commands';
import { ITestResult, TestStatus } from './runners/models';
class TestStatusBarProvider implements Disposable {
private readonly statusBarItem: StatusBarItem;
constructor() {
this.statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left, Number.MIN_VALUE);
}
public show(): void {
this.statusBarItem.show();
}
public showRunningTest(): void {
this.update('$(sync~spin) Running tests...', 'Show test output', JavaTestRunnerCommands.SHOW_TEST_OUTPUT);
}
public showFailure(): void {
this.update('$(issue-opened) Failed to run tests', 'Show test output', JavaTestRunnerCommands.SHOW_TEST_OUTPUT);
}
public showTestResult(results: ITestResult[]): void {
if (results.length === 0) {
this.statusBarItem.hide();
return;
}
let failedNum: number = 0;
let passedNum: number = 0;
for (const result of results) {
if (result.status === TestStatus.Fail) {
failedNum++;
} else if (result.status === TestStatus.Pass) {
passedNum++;
}
}
this.statusBarItem.accessibilityInformation = {
label: `${failedNum} failed, ${passedNum} passed test${failedNum + passedNum === 1 ? '' : 's'}`,
};
this.update(`$(x) ${failedNum} $(check) ${passedNum}`, 'Show test report', JavaTestRunnerCommands.SHOW_TEST_REPORT, [results]);
}
public update(text: string, tooltip?: string, command?: string, args?: any[]): void {
this.statusBarItem.text = text;
this.statusBarItem.tooltip = tooltip;
if (command) {
this.statusBarItem.command = {
title: text,
command,
arguments: args,
};
}
this.statusBarItem.show();
}
public dispose(): void {
this.statusBarItem.dispose();
}
}
export const testStatusBarProvider: TestStatusBarProvider = new TestStatusBarProvider();

44
src/types.ts Normal file
Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Range, TestItem, TestRun, WorkspaceFolder } from 'vscode';
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,
}
export interface IRunTestContext {
isDebug: boolean;
kind: TestKind;
projectName: string;
testItems: TestItem[];
testRun: TestRun;
workspaceFolder: WorkspaceFolder;
}

Просмотреть файл

@ -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;
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше