mirror of
https://github.com/Microsoft/vscode
synced 2024-08-28 13:29:44 +00:00
testing: fix unit tests
This commit is contained in:
parent
e2c9137841
commit
01089c0a50
|
@ -60,6 +60,7 @@ const compilations = [
|
|||
'python/tsconfig.json',
|
||||
'search-result/tsconfig.json',
|
||||
'simple-browser/tsconfig.json',
|
||||
'testing-editor-contributions/tsconfig.json',
|
||||
'typescript-language-features/test-workspace/tsconfig.json',
|
||||
'typescript-language-features/tsconfig.json',
|
||||
'vscode-api-tests/tsconfig.json',
|
||||
|
|
|
@ -35,6 +35,7 @@ exports.dirs = [
|
|||
'extensions/php-language-features',
|
||||
'extensions/search-result',
|
||||
'extensions/simple-browser',
|
||||
'extensions/testing-editor-contributions',
|
||||
'extensions/typescript-language-features',
|
||||
'extensions/vscode-api-tests',
|
||||
'extensions/vscode-colorize-tests',
|
||||
|
|
6
extensions/testing-editor-contributions/.vscodeignore
Normal file
6
extensions/testing-editor-contributions/.vscodeignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
src/**
|
||||
out/**
|
||||
tsconfig.json
|
||||
extension.webpack.config.js
|
||||
extension-browser.webpack.config.js
|
||||
yarn.lock
|
5
extensions/testing-editor-contributions/README.md
Normal file
5
extensions/testing-editor-contributions/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Testing Editor Contributions
|
||||
|
||||
**Notice:** This extension is bundled with Visual Studio Code. It can be disabled but not uninstalled.
|
||||
|
||||
Provides the in-editor experience for tests and test results
|
|
@ -0,0 +1,22 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withBrowserDefaults = require('../shared.webpack.config').browser;
|
||||
const path = require('path');
|
||||
|
||||
module.exports = withBrowserDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/extension.ts'
|
||||
},
|
||||
output: {
|
||||
filename: 'extension.js',
|
||||
path: path.join(__dirname, 'dist')
|
||||
}
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
|
||||
'use strict';
|
||||
|
||||
const withDefaults = require('../shared.webpack.config');
|
||||
|
||||
module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
resolve: {
|
||||
mainFields: ['module', 'main']
|
||||
},
|
||||
entry: {
|
||||
extension: './src/extension.ts',
|
||||
}
|
||||
});
|
48
extensions/testing-editor-contributions/package.json
Normal file
48
extensions/testing-editor-contributions/package.json
Normal file
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "testing-editor-contributions",
|
||||
"displayName": "%displayName%",
|
||||
"description": "%description%",
|
||||
"version": "1.0.0",
|
||||
"enableProposedApi": true,
|
||||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"vscode": "^1.39.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"browser": "./dist/extension.js",
|
||||
"activationEvents": [
|
||||
"onStartupFinished"
|
||||
],
|
||||
"dependencies": {
|
||||
"vscode-nls": "^5.0.0"
|
||||
},
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "Testing",
|
||||
"properties": {
|
||||
"testing.enableCodeLens": {
|
||||
"description": "%config.enableCodeLens%",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"testing.enableProblemDiagnostics": {
|
||||
"description": "%config.enableProblemDiagnostics%",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:testing-editor-contributions ./tsconfig.json"
|
||||
},
|
||||
"prettier": {
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
}
|
19
extensions/testing-editor-contributions/package.nls.json
Normal file
19
extensions/testing-editor-contributions/package.nls.json
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"displayName": "Testing Editor Contributions",
|
||||
"description": "Provides the in-editor experience for tests and test results.",
|
||||
|
||||
"action.run": "Run Tests",
|
||||
"action.debug": "Debug",
|
||||
|
||||
"tooltip.run": "Run {0}",
|
||||
"tooltip.debug": "Debug {0}",
|
||||
"tooltip.runState": "{0}/{0} Tests Passed",
|
||||
"tooltip.runStateWithDuration": "{0}/{1} Tests Passed in {2}",
|
||||
|
||||
"state.failed": "Failed",
|
||||
"state.passed": "Passed",
|
||||
"state.passedWithDuration": "Passed in {0}",
|
||||
|
||||
"config.enableCodeLens": "Whether code lens on test cases and suites should be visible",
|
||||
"config.enableProblemDiagnostics": "Whether test failures should be reported in the 'problems' view and show as errors in the editor."
|
||||
}
|
412
extensions/testing-editor-contributions/src/extension.ts
Normal file
412
extensions/testing-editor-contributions/src/extension.ts
Normal file
|
@ -0,0 +1,412 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
const enum Constants {
|
||||
ConfigSection = 'testing',
|
||||
EnableCodeLensConfig = 'enableCodeLens',
|
||||
EnableDiagnosticsConfig = 'enableProblemDiagnostics',
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
const diagnostics = vscode.languages.createDiagnosticCollection();
|
||||
const services = new TestingEditorServices(diagnostics);
|
||||
context.subscriptions.push(
|
||||
services,
|
||||
diagnostics,
|
||||
vscode.languages.registerCodeLensProvider({ scheme: 'file' }, services),
|
||||
);
|
||||
}
|
||||
|
||||
class TestingConfig implements IDisposable {
|
||||
private section = vscode.workspace.getConfiguration(Constants.ConfigSection);
|
||||
private readonly changeEmitter = new vscode.EventEmitter<void>();
|
||||
private readonly listener = vscode.workspace.onDidChangeConfiguration(evt => {
|
||||
if (evt.affectsConfiguration(Constants.ConfigSection)) {
|
||||
this.section = vscode.workspace.getConfiguration(Constants.ConfigSection);
|
||||
this.changeEmitter.fire();
|
||||
}
|
||||
});
|
||||
|
||||
public readonly onChange = this.changeEmitter.event;
|
||||
|
||||
public get codeLens() {
|
||||
return this.section.get(Constants.EnableCodeLensConfig, true);
|
||||
}
|
||||
|
||||
public get diagnostics() {
|
||||
return this.section.get(Constants.EnableDiagnosticsConfig, true);
|
||||
}
|
||||
|
||||
public get isEnabled() {
|
||||
return this.codeLens || this.diagnostics;
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.listener.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class TestingEditorServices implements IDisposable, vscode.CodeLensProvider {
|
||||
private readonly codeLensChangeEmitter = new vscode.EventEmitter<void>();
|
||||
private readonly documents = new Map<string, DocumentTestObserver>();
|
||||
private readonly config = new TestingConfig();
|
||||
private disposables: IDisposable[];
|
||||
private wasEnabled = this.config.isEnabled;
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public readonly onDidChangeCodeLenses = this.codeLensChangeEmitter.event;
|
||||
|
||||
constructor(private readonly diagnostics: vscode.DiagnosticCollection) {
|
||||
this.disposables = [
|
||||
new vscode.Disposable(() => this.expireAll()),
|
||||
|
||||
this.config,
|
||||
|
||||
vscode.window.onDidChangeVisibleTextEditors((editors) => {
|
||||
if (!this.config.isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const expiredEditors = new Set(this.documents.keys());
|
||||
for (const editor of editors) {
|
||||
const key = editor.document.uri.toString();
|
||||
this.ensure(key, editor.document);
|
||||
expiredEditors.delete(key);
|
||||
}
|
||||
|
||||
for (const expired of expiredEditors) {
|
||||
this.expire(expired);
|
||||
}
|
||||
}),
|
||||
|
||||
vscode.workspace.onDidCloseTextDocument((document) => {
|
||||
this.expire(document.uri.toString());
|
||||
}),
|
||||
|
||||
this.config.onChange(() => {
|
||||
if (!this.wasEnabled || this.config.isEnabled) {
|
||||
this.attachToAllVisible();
|
||||
} else if (this.wasEnabled || !this.config.isEnabled) {
|
||||
this.expireAll();
|
||||
}
|
||||
|
||||
this.wasEnabled = this.config.isEnabled;
|
||||
this.codeLensChangeEmitter.fire();
|
||||
}),
|
||||
];
|
||||
|
||||
if (this.config.isEnabled) {
|
||||
this.attachToAllVisible();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public provideCodeLenses(document: vscode.TextDocument) {
|
||||
if (!this.config.codeLens) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.documents.get(document.uri.toString())?.provideCodeLenses() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach to all currently visible editors.
|
||||
*/
|
||||
private attachToAllVisible() {
|
||||
for (const editor of vscode.window.visibleTextEditors) {
|
||||
this.ensure(editor.document.uri.toString(), editor.document);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unattaches to all tests.
|
||||
*/
|
||||
private expireAll() {
|
||||
for (const observer of this.documents.values()) {
|
||||
observer.dispose();
|
||||
}
|
||||
|
||||
this.documents.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to tests for the document URI.
|
||||
*/
|
||||
private ensure(key: string, document: vscode.TextDocument) {
|
||||
const state = this.documents.get(key);
|
||||
if (!state) {
|
||||
const observer = new DocumentTestObserver(document, this.diagnostics, this.config);
|
||||
this.documents.set(key, observer);
|
||||
observer.onDidChangeCodeLenses(() => this.config.codeLens && this.codeLensChangeEmitter.fire());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expires and removes the watcher for the document.
|
||||
*/
|
||||
private expire(key: string) {
|
||||
const observer = this.documents.get(key);
|
||||
if (!observer) {
|
||||
return;
|
||||
}
|
||||
|
||||
observer.dispose();
|
||||
this.documents.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public dispose() {
|
||||
this.disposables.forEach((d) => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentTestObserver implements IDisposable {
|
||||
private readonly codeLensChangeEmitter = new vscode.EventEmitter<void>();
|
||||
private readonly observer = vscode.test.createDocumentTestObserver(this.document);
|
||||
private readonly disposables: IDisposable[];
|
||||
public readonly onDidChangeCodeLenses = this.codeLensChangeEmitter.event;
|
||||
private didHaveDiagnostics = this.config.diagnostics;
|
||||
|
||||
constructor(
|
||||
private readonly document: vscode.TextDocument,
|
||||
private readonly diagnostics: vscode.DiagnosticCollection,
|
||||
private readonly config: TestingConfig,
|
||||
) {
|
||||
this.disposables = [
|
||||
this.observer,
|
||||
this.codeLensChangeEmitter,
|
||||
|
||||
config.onChange(() => {
|
||||
if (this.didHaveDiagnostics && !config.diagnostics) {
|
||||
this.diagnostics.set(document.uri, []);
|
||||
} else if (!this.didHaveDiagnostics && config.diagnostics) {
|
||||
this.updateDiagnostics();
|
||||
}
|
||||
|
||||
this.didHaveDiagnostics = config.diagnostics;
|
||||
}),
|
||||
|
||||
this.observer.onDidChangeTest(() => {
|
||||
this.updateDiagnostics();
|
||||
this.codeLensChangeEmitter.fire();
|
||||
}),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
private updateDiagnostics() {
|
||||
if (!this.config.diagnostics) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uriString = this.document.uri.toString();
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
for (const test of iterateOverTests(this.observer.tests)) {
|
||||
for (const message of test.state.messages) {
|
||||
if (message.location?.uri.toString() === uriString) {
|
||||
diagnostics.push({
|
||||
range: message.location.range,
|
||||
message: message.message.toString(),
|
||||
severity: testToDiagnosticSeverity(message.severity),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.diagnostics.set(this.document.uri, diagnostics);
|
||||
}
|
||||
|
||||
public provideCodeLenses(): vscode.CodeLens[] {
|
||||
const lenses: vscode.CodeLens[] = [];
|
||||
|
||||
for (const test of iterateOverTests(this.observer.tests)) {
|
||||
const { debuggable = false, runnable = true } = test;
|
||||
if (!test.location || !(debuggable || runnable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const summary = summarize(test);
|
||||
|
||||
lenses.push({
|
||||
isResolved: true,
|
||||
range: test.location.range,
|
||||
command: {
|
||||
title: `$(${testStateToIcon[summary.computedState]}) ${getLabelFor(test, summary)}`,
|
||||
command: 'vscode.runTests',
|
||||
arguments: [[test]],
|
||||
tooltip: localize('tooltip.debug', 'Debug {0}', test.label),
|
||||
},
|
||||
});
|
||||
|
||||
if (debuggable) {
|
||||
lenses.push({
|
||||
isResolved: true,
|
||||
range: test.location.range,
|
||||
command: {
|
||||
title: localize('action.debug', 'Debug'),
|
||||
command: 'vscode.debugTests',
|
||||
arguments: [[test]],
|
||||
tooltip: localize('tooltip.debug', 'Debug {0}', test.label),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return lenses;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
public dispose() {
|
||||
this.diagnostics.set(this.document.uri, []);
|
||||
this.disposables.forEach(d => d.dispose());
|
||||
}
|
||||
}
|
||||
|
||||
function getLabelFor(test: vscode.TestItem, summary: ITestSummary) {
|
||||
if (summary.duration !== undefined) {
|
||||
return localize(
|
||||
'tooltip.runStateWithDuration',
|
||||
'{0}/{1} Tests Passed in {2}',
|
||||
summary.passed,
|
||||
summary.passed + summary.failed,
|
||||
formatDuration(summary.duration),
|
||||
);
|
||||
}
|
||||
|
||||
if (summary.passed > 0 || summary.failed > 0) {
|
||||
return localize('tooltip.runState', '{0}/{1} Tests Passed', summary.passed, summary.failed);
|
||||
}
|
||||
|
||||
if (test.state.runState === vscode.TestRunState.Passed) {
|
||||
return test.state.duration !== undefined
|
||||
? localize('state.passedWithDuration', 'Passed in {0}', formatDuration(test.state.duration))
|
||||
: localize('state.passed', 'Passed');
|
||||
}
|
||||
|
||||
if (isFailedState(test.state.runState)) {
|
||||
return localize('state.failed', 'Failed');
|
||||
}
|
||||
|
||||
return localize('action.run', 'Run Tests');
|
||||
}
|
||||
|
||||
function formatDuration(duration: number) {
|
||||
if (duration < 1_000) {
|
||||
return `${Math.round(duration)}ms`;
|
||||
}
|
||||
|
||||
if (duration < 100_000) {
|
||||
return `${(duration / 1000).toPrecision(3)}s`;
|
||||
}
|
||||
|
||||
return `${(duration / 1000 / 60).toPrecision(3)}m`;
|
||||
}
|
||||
|
||||
const statePriority: { [K in vscode.TestRunState]: number } = {
|
||||
[vscode.TestRunState.Running]: 6,
|
||||
[vscode.TestRunState.Queued]: 5,
|
||||
[vscode.TestRunState.Errored]: 4,
|
||||
[vscode.TestRunState.Failed]: 3,
|
||||
[vscode.TestRunState.Passed]: 2,
|
||||
[vscode.TestRunState.Skipped]: 1,
|
||||
[vscode.TestRunState.Unset]: 0,
|
||||
};
|
||||
|
||||
const maxPriority = (a: vscode.TestRunState, b: vscode.TestRunState) =>
|
||||
statePriority[a] > statePriority[b] ? a : b;
|
||||
|
||||
const isFailedState = (s: vscode.TestRunState) =>
|
||||
s === vscode.TestRunState.Failed || s === vscode.TestRunState.Errored;
|
||||
|
||||
interface ITestSummary {
|
||||
passed: number;
|
||||
failed: number;
|
||||
duration: number | undefined;
|
||||
computedState: vscode.TestRunState;
|
||||
}
|
||||
|
||||
function summarize(test: vscode.TestItem) {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
let duration: number | undefined;
|
||||
let computedState = test.state.runState;
|
||||
|
||||
const queue = test.children ? [test.children] : [];
|
||||
while (queue.length) {
|
||||
for (const test of queue.pop()!) {
|
||||
computedState = maxPriority(computedState, test.state.runState);
|
||||
if (test.state.runState === vscode.TestRunState.Passed) {
|
||||
passed++;
|
||||
if (test.state.duration !== undefined) {
|
||||
duration = test.state.duration + (duration ?? 0);
|
||||
}
|
||||
} else if (isFailedState(test.state.runState)) {
|
||||
failed++;
|
||||
if (test.state.duration !== undefined) {
|
||||
duration = test.state.duration + (duration ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
if (test.children) {
|
||||
queue.push(test.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { passed, failed, duration, computedState };
|
||||
}
|
||||
|
||||
function* iterateOverTests(tests: ReadonlyArray<vscode.TestItem>) {
|
||||
const queue = [tests];
|
||||
while (queue.length) {
|
||||
for (const test of queue.pop()!) {
|
||||
yield test;
|
||||
if (test.children) {
|
||||
queue.push(test.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const testStateToIcon: { [K in vscode.TestRunState]: string } = {
|
||||
[vscode.TestRunState.Errored]: 'testing-error-icon',
|
||||
[vscode.TestRunState.Failed]: 'testing-failed-icon',
|
||||
[vscode.TestRunState.Passed]: 'testing-passed-icon',
|
||||
[vscode.TestRunState.Queued]: 'testing-queued-icon',
|
||||
[vscode.TestRunState.Skipped]: 'testing-skipped-icon',
|
||||
[vscode.TestRunState.Unset]: 'beaker',
|
||||
[vscode.TestRunState.Running]: 'loading~spin',
|
||||
};
|
||||
|
||||
const testToDiagnosticSeverity = (severity: vscode.TestMessageSeverity | undefined) => {
|
||||
switch (severity) {
|
||||
case vscode.TestMessageSeverity.Hint:
|
||||
return vscode.DiagnosticSeverity.Hint;
|
||||
case vscode.TestMessageSeverity.Information:
|
||||
return vscode.DiagnosticSeverity.Information;
|
||||
case vscode.TestMessageSeverity.Warning:
|
||||
return vscode.DiagnosticSeverity.Warning;
|
||||
case vscode.TestMessageSeverity.Error:
|
||||
default:
|
||||
return vscode.DiagnosticSeverity.Error;
|
||||
}
|
||||
};
|
7
extensions/testing-editor-contributions/src/typings/refs.d.ts
vendored
Normal file
7
extensions/testing-editor-contributions/src/typings/refs.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/vs/vscode.proposed.d.ts'/>
|
9
extensions/testing-editor-contributions/tsconfig.json
Normal file
9
extensions/testing-editor-contributions/tsconfig.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"extends": "../shared.tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out",
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
]
|
||||
}
|
8
extensions/testing-editor-contributions/yarn.lock
Normal file
8
extensions/testing-editor-contributions/yarn.lock
Normal file
|
@ -0,0 +1,8 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
vscode-nls@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
|
||||
integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==
|
14
src/vs/vscode.proposed.d.ts
vendored
14
src/vs/vscode.proposed.d.ts
vendored
|
@ -2090,13 +2090,11 @@ declare module 'vscode' {
|
|||
readonly onDidChangeTest: Event<T>;
|
||||
|
||||
/**
|
||||
* An event that should be fired when all tests that are currently defined
|
||||
* have been discovered. The provider should continue to watch for changes
|
||||
* and fire `onDidChangeTest` until the hierarchy is disposed.
|
||||
*
|
||||
* @todo can this be covered by existing progress apis? Or return a promise
|
||||
* Promise that should be resolved when all tests that are initially
|
||||
* defined have been discovered. The provider should continue to watch for
|
||||
* changes and fire `onDidChangeTest` until the hierarchy is disposed.
|
||||
*/
|
||||
readonly onDidDiscoverInitialTests: Event<void>;
|
||||
readonly discoveredInitialTests?: Thenable<unknown>;
|
||||
|
||||
/**
|
||||
* Dispose will be called when there are no longer observers interested
|
||||
|
@ -2126,7 +2124,7 @@ declare module 'vscode' {
|
|||
* there is a previous undisposed watcher for the given workspace folder.
|
||||
*/
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
createWorkspaceTestHierarchy?(workspace: WorkspaceFolder): TestHierarchy<T>;
|
||||
createWorkspaceTestHierarchy?(workspace: WorkspaceFolder): TestHierarchy<T> | undefined;
|
||||
|
||||
/**
|
||||
* Requests that tests be provided for the given document. This will
|
||||
|
@ -2134,7 +2132,7 @@ declare module 'vscode' {
|
|||
* for instance by code lens UI.
|
||||
*/
|
||||
// eslint-disable-next-line vscode-dts-provider-naming
|
||||
createDocumentTestHierarchy?(document: TextDocument): TestHierarchy<T>;
|
||||
createDocumentTestHierarchy?(document: TextDocument): TestHierarchy<T> | undefined;
|
||||
|
||||
/**
|
||||
* Starts a test run. This should cause {@link onDidChangeTest} to
|
||||
|
|
|
@ -4,13 +4,30 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { getTestSubscriptionKey, RunTestsRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { ExtHostContext, ExtHostTestingResource, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
const reviveDiff = (diff: TestsDiff) => {
|
||||
for (const entry of diff) {
|
||||
if (entry[0] === TestDiffOpType.Add || entry[0] === TestDiffOpType.Update) {
|
||||
const item = entry[1];
|
||||
if (item.item.location) {
|
||||
item.item.location.uri = URI.revive(item.item.location.uri);
|
||||
}
|
||||
|
||||
for (const message of item.item.state.messages) {
|
||||
if (message.location) {
|
||||
message.location.uri = URI.revive(message.location.uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTesting)
|
||||
export class MainThreadTesting extends Disposable implements MainThreadTestingShape {
|
||||
private readonly proxy: ExtHostTestingShape;
|
||||
|
@ -76,6 +93,7 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
|||
* @inheritdoc
|
||||
*/
|
||||
public $publishDiff(resource: ExtHostTestingResource, uri: UriComponents, diff: TestsDiff): void {
|
||||
reviveDiff(diff);
|
||||
this.testService.publishDiff(resource, URI.revive(uri), diff);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
|||
import { TestItem } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { Disposable, RequiredTestItem } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { AbstractIncrementalTestCollection, EMPTY_TEST_RESULT, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, RunTestForProviderRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
const getTestSubscriptionKey = (resource: ExtHostTestingResource, uri: URI) => `${resource}:${uri.toString()}`;
|
||||
|
@ -148,7 +148,7 @@ export class ExtHostTesting implements ExtHostTestingShape {
|
|||
updateDelta(1);
|
||||
disposable.add(hierarchy);
|
||||
collection.addRoot(hierarchy.root, id);
|
||||
hierarchy.onDidDiscoverInitialTests(() => updateDelta(-1));
|
||||
Promise.resolve(hierarchy.discoveredInitialTests).then(() => updateDelta(-1));
|
||||
hierarchy.onDidChangeTest(e => collection.onItemChange(e, id));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
@ -669,7 +669,7 @@ class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
|
|||
}
|
||||
|
||||
public toJSON() {
|
||||
const serialized: RequiredTestItem = {
|
||||
const serialized: RequiredTestItem & TestIdWithProvider = {
|
||||
label: this.label,
|
||||
description: this.description,
|
||||
state: this.state,
|
||||
|
@ -677,6 +677,9 @@ class ExtHostTestItem implements vscode.TestItem, RequiredTestItem {
|
|||
runnable: this.runnable,
|
||||
debuggable: this.debuggable,
|
||||
children: this.children.map(c => (c as ExtHostTestItem).toJSON()),
|
||||
|
||||
providerId: this.#internal.providerId,
|
||||
testId: this.#internal.id,
|
||||
};
|
||||
|
||||
return serialized;
|
||||
|
@ -697,6 +700,7 @@ abstract class AbstractTestObserverFactory {
|
|||
const resourceKey = resourceUri.toString();
|
||||
const resource = this.resources.get(resourceKey) ?? this.createObserverData(resourceUri);
|
||||
|
||||
resource.pendingDeletion?.dispose();
|
||||
resource.observers++;
|
||||
|
||||
return {
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
|
||||
import { localize } from 'vs/nls';
|
||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as ViewContainerExtensions, IViewContainersRegistry, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
|
@ -15,6 +17,7 @@ import { ITestingCollectionService, TestingCollectionService } from 'vs/workbenc
|
|||
import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
|
||||
import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer';
|
||||
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl';
|
||||
|
@ -70,3 +73,19 @@ registerAction2(Action.TestingViewAsTreeAction);
|
|||
registerAction2(Action.CancelTestRunAction);
|
||||
registerAction2(Action.RunSelectedAction);
|
||||
registerAction2(Action.DebugSelectedAction);
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'vscode.runTests',
|
||||
handler: async (accessor: ServicesAccessor, tests: TestIdWithProvider[]) => {
|
||||
const testService = accessor.get(ITestService);
|
||||
testService.runTests({ debug: false, tests: tests.filter(t => t.providerId && t.testId) });
|
||||
}
|
||||
});
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'vscode.debugTests',
|
||||
handler: async (accessor: ServicesAccessor, tests: TestIdWithProvider[]) => {
|
||||
const testService = accessor.get(ITestService);
|
||||
testService.runTests({ debug: true, tests: tests.filter(t => t.providerId && t.testId) });
|
||||
}
|
||||
});
|
||||
|
|
|
@ -213,7 +213,7 @@ export class TestingExplorerViewModel extends Disposable {
|
|||
}
|
||||
|
||||
editorService.openEditor({
|
||||
resource: URI.revive(location.uri),
|
||||
resource: location.uri,
|
||||
options: { selection: location.range, preserveFocus: true }
|
||||
});
|
||||
}));
|
||||
|
@ -434,7 +434,7 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
|
|||
const test = element.test;
|
||||
if (test) {
|
||||
if (test.item.location) {
|
||||
label.resource = URI.revive(test.item.location.uri);
|
||||
label.resource = test.item.location.uri;
|
||||
}
|
||||
|
||||
options.title = 'hover title';
|
||||
|
@ -529,15 +529,7 @@ class HierarchalElement implements ITestTreeElement {
|
|||
}
|
||||
|
||||
public get location() {
|
||||
const location = this.test.item.location;
|
||||
if (!location) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
uri: URI.revive(location.uri),
|
||||
range: location.range,
|
||||
};
|
||||
return this.test.item.location;
|
||||
}
|
||||
|
||||
constructor(public readonly test: InternalTestItem, public readonly parentItem: HierarchalFolder | HierarchalElement) {
|
||||
|
|
|
@ -121,6 +121,10 @@ export class TestService extends Disposable implements ITestService {
|
|||
});
|
||||
}).filter(isDefined);
|
||||
|
||||
if (requests.length === 0) {
|
||||
return EMPTY_TEST_RESULT;
|
||||
}
|
||||
|
||||
this.runningTests.set(req, cancelSource);
|
||||
this.runStartedEmitter.fire(req);
|
||||
this.isRunning.set(true);
|
||||
|
|
|
@ -22,6 +22,8 @@ const stubTest = (label: string): TestItem => ({
|
|||
const simplify = (item: TestItem) => {
|
||||
if ('toJSON' in item) {
|
||||
item = (item as any).toJSON();
|
||||
delete (item as any).providerId;
|
||||
delete (item as any).testId;
|
||||
}
|
||||
|
||||
return { ...item, children: undefined };
|
||||
|
|
Loading…
Reference in a new issue