mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 04:49:35 +00:00
testing: initial output correlation
This commit is contained in:
parent
cb52f1a50e
commit
fa8ccff4d8
16
src/vs/vscode.proposed.d.ts
vendored
16
src/vs/vscode.proposed.d.ts
vendored
|
@ -1788,6 +1788,22 @@ declare module 'vscode' {
|
|||
}
|
||||
//#endregion
|
||||
|
||||
//#region non-error test output https://github.com/microsoft/vscode/issues/129201
|
||||
interface TestRun {
|
||||
/**
|
||||
* Appends raw output from the test runner. On the user's request, the
|
||||
* output will be displayed in a terminal. ANSI escape sequences,
|
||||
* such as colors and text styles, are supported.
|
||||
*
|
||||
* @param output Output text to append.
|
||||
* @param location Indicate that the output was logged at the given
|
||||
* location.
|
||||
* @param test Test item to associate the output with.
|
||||
*/
|
||||
appendOutput(output: string, location?: Location, test?: TestItem): void;
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region test tags https://github.com/microsoft/vscode/issues/129456
|
||||
/**
|
||||
* Tags can be associated with {@link TestItem | TestItems} and
|
||||
|
|
|
@ -17,7 +17,7 @@ import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
|||
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { IMainThreadTestController, ITestRootProvider, ITestService } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { ExtHostContext, ExtHostTestingShape, IExtHostContext, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
import { ExtHostContext, ExtHostTestingShape, IExtHostContext, ILocationDto, MainContext, MainThreadTestingShape } from '../common/extHost.protocol';
|
||||
|
||||
const reviveDiff = (diff: TestsDiff) => {
|
||||
for (const entry of diff) {
|
||||
|
@ -163,8 +163,13 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public $appendOutputToRun(runId: string, _taskId: string, output: VSBuffer): void {
|
||||
this.withLiveRun(runId, r => r.output.append(output));
|
||||
public $appendOutputToRun(runId: string, taskId: string, output: VSBuffer, locationDto?: ILocationDto, testId?: string): void {
|
||||
const location = locationDto && {
|
||||
uri: URI.revive(locationDto.uri),
|
||||
range: Range.lift(locationDto.range)
|
||||
};
|
||||
|
||||
this.withLiveRun(runId, r => r.appendOutput(output, taskId, location, testId));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1277,7 +1277,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
|||
TestTag: extHostTypes.TestTag,
|
||||
TestRunProfileKind: extHostTypes.TestRunProfileKind,
|
||||
TextSearchCompleteMessageType: TextSearchCompleteMessageType,
|
||||
TestMessageSeverity: extHostTypes.TestMessageSeverity,
|
||||
CoveredCount: extHostTypes.CoveredCount,
|
||||
FileCoverage: extHostTypes.FileCoverage,
|
||||
StatementCoverage: extHostTypes.StatementCoverage,
|
||||
|
|
|
@ -2156,7 +2156,7 @@ export interface MainThreadTestingShape {
|
|||
/** Appends a message to a test in the run. */
|
||||
$appendTestMessagesInRun(runId: string, taskId: string, testId: string, messages: ITestMessage[]): void;
|
||||
/** Appends raw output to the test run.. */
|
||||
$appendOutputToRun(runId: string, taskId: string, output: VSBuffer): void;
|
||||
$appendOutputToRun(runId: string, taskId: string, output: VSBuffer, location?: ILocationDto, testId?: string): void;
|
||||
/** Triggered when coverage is added to test results. */
|
||||
$signalCoverageAvailable(runId: string, taskId: string): void;
|
||||
/** Signals a task in a test run started. */
|
||||
|
|
|
@ -398,10 +398,26 @@ class TestRunTracker extends Disposable {
|
|||
this.proxy.$updateTestStateInRun(runId, taskId, TestId.fromExtHostTestItem(test, this.dto.controllerId).toString(), TestResultState.Passed, duration);
|
||||
}),
|
||||
//#endregion
|
||||
appendOutput: output => {
|
||||
if (!ended) {
|
||||
this.proxy.$appendOutputToRun(runId, taskId, VSBuffer.fromString(output));
|
||||
appendOutput: (output, location?: vscode.Location, test?: vscode.TestItem) => {
|
||||
if (ended) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (test) {
|
||||
if (this.dto.isIncluded(test)) {
|
||||
this.ensureTestIsKnown(test);
|
||||
} else {
|
||||
test = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
this.proxy.$appendOutputToRun(
|
||||
runId,
|
||||
taskId,
|
||||
VSBuffer.fromString(output),
|
||||
location && Convert.location.from(location),
|
||||
test && TestId.fromExtHostTestItem(test, ctrlId).toString(),
|
||||
);
|
||||
},
|
||||
end: () => {
|
||||
if (ended) {
|
||||
|
|
|
@ -31,7 +31,7 @@ import { SaveReason } from 'vs/workbench/common/editor';
|
|||
import * as notebooks from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
|
||||
import * as search from 'vs/workbench/contrib/search/common/search';
|
||||
import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestItem, ITestItemContext, ITestMessage, ITestTag, ITestTagDisplayInfo, SerializedTestResultItem } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { CoverageDetails, DetailType, ICoveredCount, IFileCoverage, ISerializedTestResults, ITestErrorMessage, ITestItem, ITestItemContext, ITestTag, ITestTagDisplayInfo, SerializedTestResultItem, TestMessageType } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
|
||||
import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
|
||||
import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
@ -1639,20 +1639,20 @@ export namespace NotebookRendererScript {
|
|||
}
|
||||
|
||||
export namespace TestMessage {
|
||||
export function from(message: vscode.TestMessage): ITestMessage {
|
||||
export function from(message: vscode.TestMessage): ITestErrorMessage {
|
||||
return {
|
||||
message: MarkdownString.fromStrict(message.message) || '',
|
||||
severity: types.TestMessageSeverity.Error,
|
||||
expectedOutput: message.expectedOutput,
|
||||
actualOutput: message.actualOutput,
|
||||
type: TestMessageType.Error,
|
||||
expected: message.expectedOutput,
|
||||
actual: message.actualOutput,
|
||||
location: message.location ? location.from(message.location) as any : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function to(item: ITestMessage): vscode.TestMessage {
|
||||
export function to(item: ITestErrorMessage): vscode.TestMessage {
|
||||
const message = new types.TestMessage(typeof item.message === 'string' ? item.message : MarkdownString.to(item.message));
|
||||
message.actualOutput = item.actualOutput;
|
||||
message.expectedOutput = item.expectedOutput;
|
||||
message.actualOutput = item.actual;
|
||||
message.expectedOutput = item.expected;
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
@ -1750,7 +1750,9 @@ export namespace TestResults {
|
|||
taskStates: item.tasks.map(t => ({
|
||||
state: t.state as number as types.TestResultState,
|
||||
duration: t.duration,
|
||||
messages: t.messages.map(TestMessage.to),
|
||||
messages: t.messages
|
||||
.filter((m): m is ITestErrorMessage => m.type === TestMessageType.Error)
|
||||
.map(TestMessage.to),
|
||||
})),
|
||||
children: item.children
|
||||
.map(c => byInternalId.get(c))
|
||||
|
|
|
@ -3302,13 +3302,6 @@ export enum TestResultState {
|
|||
Errored = 6
|
||||
}
|
||||
|
||||
export enum TestMessageSeverity {
|
||||
Error = 0,
|
||||
Warning = 1,
|
||||
Information = 2,
|
||||
Hint = 3
|
||||
}
|
||||
|
||||
export enum TestRunProfileKind {
|
||||
Run = 1,
|
||||
Debug = 2,
|
||||
|
|
|
@ -7,8 +7,7 @@ import { Codicon } from 'vs/base/common/codicons';
|
|||
import { localize } from 'vs/nls';
|
||||
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { TestMessageSeverity } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { testingColorRunAction, testMessageSeverityColors, testStatesToIconColors } from 'vs/workbench/contrib/testing/browser/theme';
|
||||
import { testingColorRunAction, testStatesToIconColors } from 'vs/workbench/contrib/testing/browser/theme';
|
||||
import { TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
|
||||
export const testingViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.'));
|
||||
|
@ -37,13 +36,6 @@ export const testingStatesToIcons = new Map<TestResultState, ThemeIcon>([
|
|||
[TestResultState.Unset, registerIcon('testing-unset-icon', Codicon.circleOutline, localize('testingUnsetIcon', 'Icon shown for tests that are in an unset state.'))],
|
||||
]);
|
||||
|
||||
export const testMessageSeverityToIcons = new Map<TestMessageSeverity, ThemeIcon>([
|
||||
[TestMessageSeverity.Error, registerIcon('testing-error-message-icon', Codicon.error, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
|
||||
[TestMessageSeverity.Warning, registerIcon('testing-warning-message-icon', Codicon.warning, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
|
||||
[TestMessageSeverity.Information, registerIcon('testing-info-message-icon', Codicon.info, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
|
||||
[TestMessageSeverity.Hint, registerIcon('testing-hint-message-icon', Codicon.question, localize('testingErrorIcon', 'Icon shown for tests that have an error.'))],
|
||||
]);
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
for (const [state, icon] of testingStatesToIcons.entries()) {
|
||||
const color = testStatesToIconColors[state];
|
||||
|
@ -55,16 +47,6 @@ registerThemingParticipant((theme, collector) => {
|
|||
}`);
|
||||
}
|
||||
|
||||
for (const [state, { decorationForeground }] of Object.entries(testMessageSeverityColors)) {
|
||||
const icon = testMessageSeverityToIcons.get(Number(state));
|
||||
if (!icon) {
|
||||
continue;
|
||||
}
|
||||
collector.addRule(`.monaco-workbench ${ThemeIcon.asCSSSelector(icon)} {
|
||||
color: ${theme.getColor(decorationForeground)} !important;
|
||||
}`);
|
||||
}
|
||||
|
||||
collector.addRule(`
|
||||
.monaco-editor ${ThemeIcon.asCSSSelector(testingRunIcon)},
|
||||
.monaco-editor ${ThemeIcon.asCSSSelector(testingRunAllIcon)} {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Action, IAction, Separator, SubmenuAction } from 'vs/base/common/action
|
|||
import { Event } from 'vs/base/common/event';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { Disposable, DisposableStore, IDisposable, IReference } from 'vs/base/common/lifecycle';
|
||||
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
|
||||
|
@ -16,7 +17,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
|||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
import { overviewRulerError, overviewRulerInfo, overviewRulerWarning } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { overviewRulerError, overviewRulerInfo } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
@ -26,7 +27,6 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IThemeService, themeColorFromId, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { TestMessageSeverity } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { BREAKPOINT_EDITOR_CONTRIBUTION_ID, IBreakpointEditorContribution } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { getTestItemContextOverlay } from 'vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay';
|
||||
import { testingRunAllIcon, testingRunIcon, testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
|
@ -34,7 +34,7 @@ import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browse
|
|||
import { testMessageSeverityColors } from 'vs/workbench/contrib/testing/browser/theme';
|
||||
import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { labelForTestInState } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunProfile, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunProfile, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
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';
|
||||
|
@ -195,7 +195,7 @@ export class TestingDecorations extends Disposable implements IEditorContributio
|
|||
for (let i = 0; i < state.messages.length; i++) {
|
||||
const m = state.messages[i];
|
||||
if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) {
|
||||
const uri = buildTestUri({
|
||||
const uri = m.type === TestMessageType.Info ? undefined : buildTestUri({
|
||||
type: TestUriType.ResultActualOutput,
|
||||
messageIndex: i,
|
||||
taskIndex: taskId,
|
||||
|
@ -582,13 +582,14 @@ class TestMessageDecoration implements ITestDecoration {
|
|||
|
||||
constructor(
|
||||
public readonly testMessage: ITestMessage,
|
||||
private readonly messageUri: URI,
|
||||
private readonly messageUri: URI | undefined,
|
||||
public readonly location: IRichLocation,
|
||||
private readonly editor: ICodeEditor,
|
||||
@ICodeEditorService private readonly editorService: ICodeEditorService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
) {
|
||||
const { severity = TestMessageSeverity.Error, message } = testMessage;
|
||||
const severity = testMessage.type;
|
||||
const message = typeof testMessage.message === 'string' ? removeAnsiEscapeCodes(testMessage.message) : testMessage.message;
|
||||
const colorTheme = themeService.getColorTheme();
|
||||
editorService.registerDecorationType('test-message-decoration', this.decorationId, {
|
||||
after: {
|
||||
|
@ -609,13 +610,9 @@ class TestMessageDecoration implements ITestDecoration {
|
|||
options.stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
|
||||
options.collapseOnReplaceEdit = true;
|
||||
|
||||
const rulerColor = severity === TestMessageSeverity.Error
|
||||
const rulerColor = severity === TestMessageType.Error
|
||||
? overviewRulerError
|
||||
: severity === TestMessageSeverity.Warning
|
||||
? overviewRulerWarning
|
||||
: severity === TestMessageSeverity.Information
|
||||
? overviewRulerInfo
|
||||
: undefined;
|
||||
: overviewRulerInfo;
|
||||
|
||||
if (rulerColor) {
|
||||
options.overviewRuler = { color: themeColorFromId(rulerColor), position: OverviewRulerLane.Right };
|
||||
|
@ -629,6 +626,10 @@ class TestMessageDecoration implements ITestDecoration {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!this.messageUri) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.target.element?.className.includes(this.decorationId)) {
|
||||
TestingOutputPeekController.get(this.editor).toggle(this.messageUri);
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/brow
|
|||
import { testingPeekBorder } from 'vs/workbench/contrib/testing/browser/theme';
|
||||
import { AutoOpenPeekViewWhen, getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { IRichLocation, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
|
||||
|
@ -646,6 +646,10 @@ class TestingOutputPeek extends PeekViewWidget {
|
|||
const message = dto.messages[dto.messageIndex];
|
||||
const previous = this.current;
|
||||
|
||||
if (message.type !== TestMessageType.Error) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (!dto.revealLocation && !previous) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -729,8 +733,8 @@ const diffEditorOptions: IDiffEditorOptions = {
|
|||
modifiedAriaLabel: localize('testingOutputActual', 'Actual result'),
|
||||
};
|
||||
|
||||
const isDiffable = (message: ITestMessage): message is ITestMessage & { actualOutput: string; expectedOutput: string } =>
|
||||
message.actualOutput !== undefined && message.expectedOutput !== undefined;
|
||||
const isDiffable = (message: ITestErrorMessage): message is ITestErrorMessage & { actualOutput: string; expectedOutput: string } =>
|
||||
message.actual !== undefined && message.expected !== undefined;
|
||||
|
||||
class DiffContentProvider extends Disposable implements IPeekOutputRenderer {
|
||||
private readonly widget = this._register(new MutableDisposable<EmbeddedDiffEditorWidget>());
|
||||
|
@ -746,7 +750,7 @@ class DiffContentProvider extends Disposable implements IPeekOutputRenderer {
|
|||
super();
|
||||
}
|
||||
|
||||
public async update({ expectedUri, actualUri }: TestDto, message: ITestMessage) {
|
||||
public async update({ expectedUri, actualUri }: TestDto, message: ITestErrorMessage) {
|
||||
if (!isDiffable(message)) {
|
||||
return this.clear();
|
||||
}
|
||||
|
@ -772,7 +776,7 @@ class DiffContentProvider extends Disposable implements IPeekOutputRenderer {
|
|||
|
||||
this.widget.value.setModel(model);
|
||||
this.widget.value.updateOptions(this.getOptions(
|
||||
isMultiline(message.expectedOutput) || isMultiline(message.actualOutput)
|
||||
isMultiline(message.expected) || isMultiline(message.actual)
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -831,7 +835,7 @@ class MarkdownTestMessagePeek extends Disposable implements IPeekOutputRenderer
|
|||
super();
|
||||
}
|
||||
|
||||
public update(_dto: TestDto, message: ITestMessage): void {
|
||||
public update(_dto: TestDto, message: ITestErrorMessage): void {
|
||||
if (isDiffable(message) || typeof message.message === 'string') {
|
||||
return this.textPreview.clear();
|
||||
}
|
||||
|
@ -862,7 +866,7 @@ class PlainTextMessagePeek extends Disposable implements IPeekOutputRenderer {
|
|||
super();
|
||||
}
|
||||
|
||||
public async update({ messageUri }: TestDto, message: ITestMessage) {
|
||||
public async update({ messageUri }: TestDto, message: ITestErrorMessage) {
|
||||
if (isDiffable(message) || typeof message.message !== 'string') {
|
||||
return this.clear();
|
||||
}
|
||||
|
@ -902,8 +906,8 @@ class PlainTextMessagePeek extends Disposable implements IPeekOutputRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
const hintDiffPeekHeight = (message: ITestMessage) =>
|
||||
Math.max(hintPeekStrHeight(message.actualOutput), hintPeekStrHeight(message.expectedOutput));
|
||||
const hintDiffPeekHeight = (message: ITestErrorMessage) =>
|
||||
Math.max(hintPeekStrHeight(message.actual), hintPeekStrHeight(message.expected));
|
||||
|
||||
const firstLine = (str: string) => {
|
||||
const index = str.indexOf('\n');
|
||||
|
@ -1039,7 +1043,6 @@ class TestMessageElement implements ITreeElement {
|
|||
public readonly context: URI;
|
||||
public readonly id: string;
|
||||
public readonly label: string;
|
||||
public readonly icon: ThemeIcon | undefined;
|
||||
public readonly uri: URI;
|
||||
public readonly location: IRichLocation | undefined;
|
||||
|
||||
|
@ -1049,7 +1052,7 @@ class TestMessageElement implements ITreeElement {
|
|||
public readonly taskIndex: number,
|
||||
public readonly messageIndex: number,
|
||||
) {
|
||||
const { message, severity, location } = test.tasks[taskIndex].messages[messageIndex];
|
||||
const { message, location } = test.tasks[taskIndex].messages[messageIndex];
|
||||
|
||||
this.location = location;
|
||||
this.uri = this.context = buildTestUri({
|
||||
|
@ -1062,7 +1065,6 @@ class TestMessageElement implements ITreeElement {
|
|||
|
||||
this.id = this.uri.toString();
|
||||
this.label = firstLine(renderStringAsPlaintext(message));
|
||||
this.icon = icons.testMessageSeverityToIcons.get(severity);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,11 +5,10 @@
|
|||
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { localize } from 'vs/nls';
|
||||
import { editorErrorForeground, editorForeground, editorHintForeground, editorInfoForeground, editorWarningForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { editorErrorForeground, editorForeground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { TestMessageSeverity } from 'vs/workbench/api/common/extHostTypes';
|
||||
import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestMessageType, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
|
||||
export const testingColorIconFailed = registerColor('testing.iconFailed', {
|
||||
dark: '#f14c4c',
|
||||
|
@ -60,12 +59,12 @@ export const testingPeekBorder = registerColor('testing.peekBorder', {
|
|||
}, localize('testing.peekBorder', 'Color of the peek view borders and arrow.'));
|
||||
|
||||
export const testMessageSeverityColors: {
|
||||
[K in TestMessageSeverity]: {
|
||||
[K in TestMessageType]: {
|
||||
decorationForeground: string,
|
||||
marginBackground: string,
|
||||
};
|
||||
} = {
|
||||
[TestMessageSeverity.Error]: {
|
||||
[TestMessageType.Error]: {
|
||||
decorationForeground: registerColor(
|
||||
'testing.message.error.decorationForeground',
|
||||
{ dark: editorErrorForeground, light: editorErrorForeground, hc: editorForeground },
|
||||
|
@ -77,42 +76,18 @@ export const testMessageSeverityColors: {
|
|||
localize('testing.message.error.marginBackground', 'Margin color beside error messages shown inline in the editor.')
|
||||
),
|
||||
},
|
||||
[TestMessageSeverity.Warning]: {
|
||||
decorationForeground: registerColor(
|
||||
'testing.message.warning.decorationForeground',
|
||||
{ dark: editorWarningForeground, light: editorWarningForeground, hc: editorForeground },
|
||||
localize('testing.message.warning.decorationForeground', 'Text color of test warning messages shown inline in the editor.')
|
||||
),
|
||||
marginBackground: registerColor(
|
||||
'testing.message.warning.lineBackground',
|
||||
{ dark: new Color(new RGBA(255, 208, 0, 0.2)), light: new Color(new RGBA(255, 208, 0, 0.2)), hc: null },
|
||||
localize('testing.message.warning.marginBackground', 'Margin color beside warning messages shown inline in the editor.')
|
||||
),
|
||||
},
|
||||
[TestMessageSeverity.Information]: {
|
||||
[TestMessageType.Info]: {
|
||||
decorationForeground: registerColor(
|
||||
'testing.message.info.decorationForeground',
|
||||
{ dark: editorInfoForeground, light: editorInfoForeground, hc: editorForeground },
|
||||
{ dark: transparent(editorForeground, 0.5), light: transparent(editorForeground, 0.5), hc: transparent(editorForeground, 0.5) },
|
||||
localize('testing.message.info.decorationForeground', 'Text color of test info messages shown inline in the editor.')
|
||||
),
|
||||
marginBackground: registerColor(
|
||||
'testing.message.info.lineBackground',
|
||||
{ dark: new Color(new RGBA(0, 127, 255, 0.2)), light: new Color(new RGBA(0, 127, 255, 0.2)), hc: null },
|
||||
{ dark: null, light: null, hc: null },
|
||||
localize('testing.message.info.marginBackground', 'Margin color beside info messages shown inline in the editor.')
|
||||
),
|
||||
},
|
||||
[TestMessageSeverity.Hint]: {
|
||||
decorationForeground: registerColor(
|
||||
'testing.message.hint.decorationForeground',
|
||||
{ dark: editorHintForeground, light: editorHintForeground, hc: editorForeground },
|
||||
localize('testing.message.hint.decorationForeground', 'Text color of test hint messages shown inline in the editor.')
|
||||
),
|
||||
marginBackground: registerColor(
|
||||
'testing.message.hint.lineBackground',
|
||||
{ dark: null, light: null, hc: editorForeground },
|
||||
localize('testing.message.hint.marginBackground', 'Margin color beside hint messages shown inline in the editor.')
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const testStatesToIconColors: { [K in TestResultState]?: string } = {
|
||||
|
|
|
@ -8,7 +8,6 @@ import { MarshalledId } from 'vs/base/common/marshalling';
|
|||
import { URI } from 'vs/base/common/uri';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { TestMessageSeverity } from 'vs/workbench/api/common/extHostTypes';
|
||||
|
||||
export const enum TestResultState {
|
||||
Unset = 0,
|
||||
|
@ -97,15 +96,28 @@ export interface IRichLocation {
|
|||
uri: URI;
|
||||
}
|
||||
|
||||
export interface ITestMessage {
|
||||
export const enum TestMessageType {
|
||||
Error,
|
||||
Info
|
||||
}
|
||||
|
||||
export interface ITestErrorMessage {
|
||||
message: string | IMarkdownString;
|
||||
/** @deprecated */
|
||||
severity: TestMessageSeverity;
|
||||
expectedOutput: string | undefined;
|
||||
actualOutput: string | undefined;
|
||||
type: TestMessageType.Error;
|
||||
expected: string | undefined;
|
||||
actual: string | undefined;
|
||||
location: IRichLocation | undefined;
|
||||
}
|
||||
|
||||
export interface ITestOutputMessage {
|
||||
message: string;
|
||||
type: TestMessageType.Info;
|
||||
offset: number;
|
||||
location: IRichLocation | undefined;
|
||||
}
|
||||
|
||||
export type ITestMessage = ITestErrorMessage | ITestOutputMessage;
|
||||
|
||||
export interface ITestTaskState {
|
||||
state: TestResultState;
|
||||
duration: number | undefined;
|
||||
|
@ -211,12 +223,10 @@ export interface ISerializedTestResults {
|
|||
id: string;
|
||||
/** Time the results were compelted */
|
||||
completedAt: number;
|
||||
/** Raw output, given for tests published by extensiosn */
|
||||
output?: string;
|
||||
/** Subset of test result items */
|
||||
items: SerializedTestResultItem[];
|
||||
/** Tasks involved in the run. */
|
||||
tasks: ITestRunTask[];
|
||||
tasks: { id: string; name: string | undefined; messages: ITestOutputMessage[] }[];
|
||||
/** Human-readable name of the test run. */
|
||||
name: string;
|
||||
/** Test trigger informaton */
|
||||
|
|
|
@ -12,15 +12,20 @@ import { Range } from 'vs/editor/common/core/range';
|
|||
import { localize } from 'vs/nls';
|
||||
import { IComputedStateAccessor, refreshComputedState } from 'vs/workbench/contrib/testing/common/getComputedState';
|
||||
import { IObservableValue, MutableObservableValue, staticObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
|
||||
import { ISerializedTestResults, ITestItem, ITestMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestItemExpandState, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { IRichLocation, ISerializedTestResults, ITestItem, ITestMessage, ITestOutputMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestItemExpandState, TestMessageType, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
||||
import { maxPriority, statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates';
|
||||
|
||||
export interface ITestRunTaskWithCoverage extends ITestRunTask {
|
||||
export interface ITestRunTaskResults extends ITestRunTask {
|
||||
/**
|
||||
* Contains test coverage for the result, if it's available.
|
||||
*/
|
||||
readonly coverage: IObservableValue<TestCoverage | undefined>;
|
||||
|
||||
/**
|
||||
* Messages from the task not associated with any specific test.
|
||||
*/
|
||||
readonly otherMessages: ITestOutputMessage[];
|
||||
}
|
||||
|
||||
export interface ITestResult {
|
||||
|
@ -58,7 +63,7 @@ export interface ITestResult {
|
|||
/**
|
||||
* List of this result's subtasks.
|
||||
*/
|
||||
tasks: ReadonlyArray<ITestRunTaskWithCoverage>;
|
||||
tasks: ReadonlyArray<ITestRunTaskResults>;
|
||||
|
||||
/**
|
||||
* Gets the state of the test by its extension-assigned ID.
|
||||
|
@ -133,6 +138,14 @@ export class LiveOutputController {
|
|||
|
||||
private readonly dataEmitter = new Emitter<VSBuffer>();
|
||||
private readonly endEmitter = new Emitter<void>();
|
||||
private _offset = 0;
|
||||
|
||||
/**
|
||||
* Gets the number of written bytes.
|
||||
*/
|
||||
public get offset() {
|
||||
return this._offset;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly writer: Lazy<[VSBufferWriteableStream, Promise<void>]>,
|
||||
|
@ -149,6 +162,7 @@ export class LiveOutputController {
|
|||
|
||||
this.previouslyWritten?.push(data);
|
||||
this.dataEmitter.fire(data);
|
||||
this._offset += data.byteLength;
|
||||
|
||||
return this.writer.getValue()[0].write(data);
|
||||
}
|
||||
|
@ -243,7 +257,7 @@ export class LiveTestResult implements ITestResult {
|
|||
|
||||
public readonly onChange = this.changeEmitter.event;
|
||||
public readonly onComplete = this.completeEmitter.event;
|
||||
public readonly tasks: ITestRunTaskWithCoverage[] = [];
|
||||
public readonly tasks: ITestRunTaskResults[] = [];
|
||||
public readonly name = localize('runFinished', 'Test run at {0}', new Date().toLocaleString());
|
||||
|
||||
/**
|
||||
|
@ -301,12 +315,32 @@ export class LiveTestResult implements ITestResult {
|
|||
return this.testById.get(extTestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends output that occurred during the test run.
|
||||
*/
|
||||
public appendOutput(output: VSBuffer, taskId: string, location?: IRichLocation, testId?: string): void {
|
||||
this.output.append(output);
|
||||
const message: ITestOutputMessage = {
|
||||
location,
|
||||
message: output.toString(),
|
||||
offset: this.output.offset,
|
||||
type: TestMessageType.Info,
|
||||
};
|
||||
|
||||
const index = this.mustGetTaskIndex(taskId);
|
||||
if (testId) {
|
||||
this.testById.get(testId)?.tasks[index].messages.push(message);
|
||||
} else {
|
||||
this.tasks[index].otherMessages.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new run task to the results.
|
||||
*/
|
||||
public addTask(task: ITestRunTask) {
|
||||
const index = this.tasks.length;
|
||||
this.tasks.push({ ...task, coverage: new MutableObservableValue(undefined) });
|
||||
this.tasks.push({ ...task, coverage: new MutableObservableValue(undefined), otherMessages: [] });
|
||||
|
||||
for (const test of this.tests) {
|
||||
test.tasks.push({ duration: undefined, messages: [], state: TestResultState.Unset });
|
||||
|
@ -504,7 +538,7 @@ export class LiveTestResult implements ITestResult {
|
|||
private readonly doSerialize = new Lazy((): ISerializedTestResults => ({
|
||||
id: this.id,
|
||||
completedAt: this.completedAt!,
|
||||
tasks: this.tasks,
|
||||
tasks: this.tasks.map(t => ({ id: t.id, name: t.name, messages: t.otherMessages })),
|
||||
name: this.name,
|
||||
request: this.request,
|
||||
items: [...this.testById.values()].map(entry => ({
|
||||
|
@ -538,7 +572,7 @@ export class HydratedTestResult implements ITestResult {
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public readonly tasks: ITestRunTaskWithCoverage[];
|
||||
public readonly tasks: ITestRunTaskResults[];
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
|
@ -566,7 +600,21 @@ export class HydratedTestResult implements ITestResult {
|
|||
) {
|
||||
this.id = serialized.id;
|
||||
this.completedAt = serialized.completedAt;
|
||||
this.tasks = serialized.tasks.map(task => ({ ...task, coverage: staticObservableValue(undefined) }));
|
||||
this.tasks = serialized.tasks.map((task, i) => ({
|
||||
id: task.id,
|
||||
name: task.name,
|
||||
running: false,
|
||||
coverage: staticObservableValue(undefined),
|
||||
otherMessages: task.messages.map(m => ({
|
||||
message: m.message,
|
||||
type: m.type,
|
||||
offset: m.offset,
|
||||
location: m.location && {
|
||||
uri: URI.revive(m.location.uri),
|
||||
range: Range.lift(m.location.range)
|
||||
},
|
||||
}))
|
||||
}));
|
||||
this.name = serialized.name;
|
||||
this.request = serialized.request;
|
||||
|
||||
|
|
|
@ -43,7 +43,12 @@ export interface ITestResultStorage {
|
|||
|
||||
export const ITestResultStorage = createDecorator('ITestResultStorage');
|
||||
|
||||
const currentRevision = 0;
|
||||
/**
|
||||
* Data revision this version of VS Code deals with. Should be bumped whenever
|
||||
* a breaking change is made to the stored results, which will cause previous
|
||||
* revisions to be discarded.
|
||||
*/
|
||||
const currentRevision = 1;
|
||||
|
||||
export abstract class BaseTestResultStorage implements ITestResultStorage {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
|
|
@ -274,7 +274,7 @@ suite('Workbench - Test Results Service', () => {
|
|||
const makeHydrated = async (completedAt = 42, state = TestResultState.Passed) => new HydratedTestResult({
|
||||
completedAt,
|
||||
id: 'some-id',
|
||||
tasks: [{ id: 't', running: false, name: undefined }],
|
||||
tasks: [{ id: 't', messages: [], name: undefined }],
|
||||
name: 'hello world',
|
||||
request: defaultOpts([]),
|
||||
items: [{
|
||||
|
|
|
@ -36,10 +36,10 @@ suite('Workbench - Test Result Storage', () => {
|
|||
if (addMessage) {
|
||||
t.appendMessage(new TestId(['ctrlId', 'id-a']).toString(), 't', {
|
||||
message: addMessage,
|
||||
actualOutput: undefined,
|
||||
expectedOutput: undefined,
|
||||
actual: undefined,
|
||||
expected: undefined,
|
||||
location: undefined,
|
||||
severity: 0,
|
||||
type: 0,
|
||||
});
|
||||
}
|
||||
t.markComplete();
|
||||
|
|
|
@ -7,6 +7,7 @@ const { constants } = require('mocha/lib/runner');
|
|||
const BaseRunner = require('mocha/lib/reporters/base');
|
||||
|
||||
const {
|
||||
EVENT_TEST_BEGIN,
|
||||
EVENT_TEST_PASS,
|
||||
EVENT_TEST_FAIL,
|
||||
EVENT_RUN_BEGIN,
|
||||
|
@ -28,6 +29,7 @@ module.exports = class FullJsonStreamReporter extends BaseRunner {
|
|||
runner.once(EVENT_RUN_BEGIN, () => writeEvent(['start', { total }]));
|
||||
runner.once(EVENT_RUN_END, () => writeEvent(['end', this.stats]));
|
||||
|
||||
runner.on(EVENT_TEST_BEGIN, test => writeEvent(['testStart', clean(test)]));
|
||||
runner.on(EVENT_TEST_PASS, test => writeEvent(['pass', clean(test)]));
|
||||
runner.on(EVENT_TEST_FAIL, (test, err) => {
|
||||
test = clean(test);
|
||||
|
|
Loading…
Reference in a new issue