From 4b80b4cd36d935a8897ecb83a7c703853fffae25 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 11 Aug 2021 14:48:30 -0700 Subject: [PATCH] testing: allow toggling test output visibility Also, just show outputs for the last test run. --- src/vs/base/test/common/arrays.test.ts | 4 +- .../testing/browser/testExplorerActions.ts | 25 +++++ .../testing/browser/testingDecorations.ts | 93 +++++++++++-------- .../contrib/testing/common/testService.ts | 9 +- .../contrib/testing/common/testServiceImpl.ts | 13 +++ 5 files changed, 100 insertions(+), 44 deletions(-) diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 09e2ced01ca..5db92f2b4ef 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -9,6 +9,7 @@ suite('Arrays', () => { test('findFirst', () => { const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69]; + console.log('hello'); let idx = arrays.findFirstInSorted(array, e => e >= 0); assert.strictEqual(array[idx], 1); @@ -20,6 +21,7 @@ suite('Arrays', () => { idx = arrays.findFirstInSorted(array, e => e >= 61); assert.strictEqual(array[idx], 61); + console.log('world'); idx = arrays.findFirstInSorted(array, e => e >= 69); assert.strictEqual(array[idx], 69); @@ -28,7 +30,7 @@ suite('Arrays', () => { assert.strictEqual(idx, array.length); idx = arrays.findFirstInSorted([], e => e >= 0); - assert.strictEqual(array[idx], 1); + assert.strictEqual(array[idx], 3); }); test('quickSelect', () => { diff --git a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts index 168cb9640e4..ef8c110a200 100644 --- a/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts +++ b/src/vs/workbench/contrib/testing/browser/testExplorerActions.ts @@ -1103,6 +1103,30 @@ export class OpenOutputPeek extends Action2 { } } +export class ToggleInlineTestOutput extends Action2 { + public static readonly ID = 'testing.toggleInlineTestOutput'; + constructor() { + super({ + id: ToggleInlineTestOutput.ID, + title: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"), + category, + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_I), + }, + menu: { + id: MenuId.CommandPalette, + when: TestingContextKeys.hasAnyResults.isEqualTo(true), + }, + }); + } + + public async run(accessor: ServicesAccessor) { + const testService = accessor.get(ITestService); + testService.showInlineOutput.value = !testService.showInlineOutput.value; + } +} + export const allTestActions = [ // todo: these are disabled until we figure out how we want autorun to work // AutoRunOffAction, @@ -1136,5 +1160,6 @@ export const allTestActions = [ TestingSortByStatusAction, TestingViewAsListAction, TestingViewAsTreeAction, + ToggleInlineTestOutput, UnhideTestAction, ]; diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index 86d4bd3de53..314f7bc83cc 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -38,6 +38,7 @@ import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMe import { isFailedState, maxPriority } from 'vs/workbench/contrib/testing/common/testingStates'; import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri'; import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; +import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { getContextForTestItem, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService'; @@ -126,17 +127,12 @@ export class TestingDecorations extends Disposable implements IEditorContributio } })); - this._register(Event.any(this.results.onResultsChanged, this.testService.excluded.onTestExclusionsChanged)(() => { - if (this.currentUri) { - this.setDecorations(this.currentUri); - } - })); - - this._register(this.testService.onDidProcessDiff(() => { - if (this.currentUri) { - this.setDecorations(this.currentUri); - } - })); + this._register(Event.any( + this.results.onResultsChanged, + this.testService.excluded.onTestExclusionsChanged, + this.testService.showInlineOutput.onDidChange, + this.testService.onDidProcessDiff, + )(() => this.setDecorations(this.currentUri))); } private attachModel(uri?: URI) { @@ -165,45 +161,56 @@ export class TestingDecorations extends Disposable implements IEditorContributio this.setDecorations(uri); } - private setDecorations(uri: URI): void { + private setDecorations(uri: URI | undefined): void { + if (!uri) { + this.clearDecorations(); + return; + } + this.editor.changeDecorations(accessor => { const newDecorations: ITestDecoration[] = []; for (const test of this.testService.collection.all) { - const stateLookup = this.results.getStateById(test.item.extId); - if (test.item.range && test.item.uri?.toString() === uri.toString()) { - const line = test.item.range.startLineNumber; - const resultItem = stateLookup?.[1]; - const existing = newDecorations.findIndex(d => d instanceof RunTestDecoration && d.line === line); - if (existing !== -1) { - newDecorations[existing] = (newDecorations[existing] as RunTestDecoration).merge(test, resultItem); - } else { - newDecorations.push(this.instantiationService.createInstance(RunSingleTestDecoration, test, this.editor, stateLookup?.[1])); - } - } - - if (!stateLookup) { + if (!test.item.range || test.item.uri?.toString() !== uri.toString()) { continue; } - const [result, stateItem] = stateLookup; - if (stateItem.retired) { - continue; // do not show decorations for outdated tests + const stateLookup = this.results.getStateById(test.item.extId); + const line = test.item.range.startLineNumber; + const resultItem = stateLookup?.[1]; + const existing = newDecorations.findIndex(d => d instanceof RunTestDecoration && d.line === line); + if (existing !== -1) { + newDecorations[existing] = (newDecorations[existing] as RunTestDecoration).merge(test, resultItem); + } else { + newDecorations.push(this.instantiationService.createInstance(RunSingleTestDecoration, test, this.editor, stateLookup?.[1])); + } + } + + const lastResult = this.results.results[0]; + if (this.testService.showInlineOutput.value && lastResult instanceof LiveTestResult) { + for (const task of lastResult.tasks) { + for (const m of task.otherMessages) { + if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) { + newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor)); + } + } } - for (let taskId = 0; taskId < stateItem.tasks.length; taskId++) { - const state = stateItem.tasks[taskId]; - for (let i = 0; i < state.messages.length; i++) { - const m = state.messages[i]; - if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) { - const uri = m.type === TestMessageType.Info ? undefined : buildTestUri({ - type: TestUriType.ResultActualOutput, - messageIndex: i, - taskIndex: taskId, - resultId: result.id, - testExtId: stateItem.item.extId, - }); + for (const test of lastResult.tests) { + for (let taskId = 0; taskId < test.tasks.length; taskId++) { + const state = test.tasks[taskId]; + for (let i = 0; i < state.messages.length; i++) { + const m = state.messages[i]; + if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) { + const uri = m.type === TestMessageType.Info ? undefined : buildTestUri({ + type: TestUriType.ResultActualOutput, + messageIndex: i, + taskIndex: taskId, + resultId: lastResult.id, + testExtId: test.item.extId, + }); - newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor)); + newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor)); + } } } } @@ -218,6 +225,10 @@ export class TestingDecorations extends Disposable implements IEditorContributio } private clearDecorations(): void { + if (!this.lastDecorations.length) { + return; + } + this.editor.changeDecorations(accessor => { for (const decoration of this.lastDecorations) { accessor.removeDecoration(decoration.id); diff --git a/src/vs/workbench/contrib/testing/common/testService.ts b/src/vs/workbench/contrib/testing/common/testService.ts index 8b95a1e9599..6f7acd078ef 100644 --- a/src/vs/workbench/contrib/testing/common/testService.ts +++ b/src/vs/workbench/contrib/testing/common/testService.ts @@ -7,11 +7,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; import * as extpath from 'vs/base/common/extpath'; import { Iterable } from 'vs/base/common/iterator'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { MarshalledId } from 'vs/base/common/marshalling'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; +import { IObservableValue, MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestItemContext, ITestTagDisplayInfo, ResolvedTestRunRequest, RunTestForControllerRequest, TestItemExpandState, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; @@ -225,6 +225,11 @@ export interface ITestService { */ readonly onDidProcessDiff: Event; + /** + * Whether inline editor decorations should be visible. + */ + readonly showInlineOutput: MutableObservableValue; + /** * Registers an interface that runs tests for the given provider ID. */ diff --git a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts index ee654440378..b92e26000a8 100644 --- a/src/vs/workbench/contrib/testing/common/testServiceImpl.ts +++ b/src/vs/workbench/contrib/testing/common/testServiceImpl.ts @@ -11,8 +11,11 @@ import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection'; +import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; +import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; import { ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection'; import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; @@ -55,9 +58,19 @@ export class TestService extends Disposable implements ITestService { */ public readonly excluded: TestExclusions; + /** + * @inheritdoc + */ + public readonly showInlineOutput = MutableObservableValue.stored(new StoredValue({ + key: 'inlineTestOutputVisible', + scope: StorageScope.WORKSPACE, + target: StorageTarget.USER + }, this.storage), true); + constructor( @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, + @IStorageService private readonly storage: IStorageService, @ITestProfileService private readonly testProfiles: ITestProfileService, @INotificationService private readonly notificationService: INotificationService, @ITestResultService private readonly testResults: ITestResultService,