mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
testing: add support for jumping to test messages in terminal output (#162406)
This commit is contained in:
parent
9fb452c485
commit
64c7d125c7
|
@ -1166,6 +1166,7 @@ class TestMessageElement implements ITreeElement {
|
|||
public readonly uri: URI;
|
||||
public readonly location?: IRichLocation;
|
||||
public readonly description?: string;
|
||||
public readonly marker?: number;
|
||||
|
||||
constructor(
|
||||
public readonly result: ITestResult,
|
||||
|
@ -1173,9 +1174,10 @@ class TestMessageElement implements ITreeElement {
|
|||
public readonly taskIndex: number,
|
||||
public readonly messageIndex: number,
|
||||
) {
|
||||
const { message, location } = test.tasks[taskIndex].messages[messageIndex];
|
||||
const m = test.tasks[taskIndex].messages[messageIndex];
|
||||
|
||||
this.location = location;
|
||||
this.location = m.location;
|
||||
this.marker = m.type === TestMessageType.Output ? m.marker : undefined;
|
||||
this.uri = this.context = buildTestUri({
|
||||
type: TestUriType.ResultMessage,
|
||||
messageIndex,
|
||||
|
@ -1186,7 +1188,7 @@ class TestMessageElement implements ITreeElement {
|
|||
|
||||
this.id = this.uri.toString();
|
||||
|
||||
const asPlaintext = renderStringAsPlaintext(message);
|
||||
const asPlaintext = renderStringAsPlaintext(m.message);
|
||||
const lines = count(asPlaintext.trimRight(), '\n');
|
||||
this.label = firstLine(asPlaintext);
|
||||
if (lines > 0) {
|
||||
|
@ -1597,6 +1599,18 @@ class TreeActionsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
if (element instanceof TestMessageElement) {
|
||||
if (element.marker !== undefined) {
|
||||
primary.push(new Action(
|
||||
'testing.outputPeek.showMessageInTerminal',
|
||||
localize('testing.showMessageInTerminal', "Show Output in Terminal"),
|
||||
Codicon.terminal.classNames,
|
||||
undefined,
|
||||
() => this.testTerminalService.open(element.result, element.marker),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
const result = { primary, secondary };
|
||||
createAndFillInActionBarActions(menu, {
|
||||
shouldForwardArgs: true,
|
||||
|
|
|
@ -17,15 +17,17 @@ import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal'
|
|||
import { testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { ITestResult } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { getMarkId } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
|
||||
|
||||
export interface ITestingOutputTerminalService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Opens a terminal for the given test's output.
|
||||
* Opens a terminal for the given test's output. Optionally, scrolls to and
|
||||
* selects the given marker in the test results.
|
||||
*/
|
||||
open(result: ITestResult): Promise<void>;
|
||||
open(result: ITestResult, marker?: number): Promise<void>;
|
||||
}
|
||||
|
||||
const friendlyDate = (date: number) => {
|
||||
|
@ -78,7 +80,7 @@ export class TestingOutputTerminalService implements ITestingOutputTerminalServi
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public async open(result: ITestResult | undefined): Promise<void> {
|
||||
public async open(result: ITestResult | undefined, marker?: number): Promise<void> {
|
||||
const testOutputPtys = this.terminalService.instances
|
||||
.map(t => {
|
||||
const output = this.outputTerminals.get(t);
|
||||
|
@ -95,6 +97,8 @@ export class TestingOutputTerminalService implements ITestingOutputTerminalServi
|
|||
} else {
|
||||
this.terminalGroupService.showPanel();
|
||||
}
|
||||
|
||||
this.revealMarker(existing[0], marker);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -114,10 +118,10 @@ export class TestingOutputTerminalService implements ITestingOutputTerminalServi
|
|||
customPtyImplementation: () => output,
|
||||
name: getTitle(result),
|
||||
},
|
||||
}), output, result);
|
||||
}), output, result, marker);
|
||||
}
|
||||
|
||||
private async showResultsInTerminal(terminal: ITerminalInstance, output: TestOutputProcess, result: ITestResult | undefined) {
|
||||
private async showResultsInTerminal(terminal: ITerminalInstance, output: TestOutputProcess, result: ITestResult | undefined, thenSelectMarker?: number) {
|
||||
this.outputTerminals.set(terminal, output);
|
||||
output.resetFor(result?.id, getTitle(result));
|
||||
this.terminalService.setActiveInstance(terminal);
|
||||
|
@ -151,9 +155,16 @@ export class TestingOutputTerminalService implements ITestingOutputTerminalServi
|
|||
const text = localize('runFinished', 'Test run finished at {0}', completedAt.toLocaleString());
|
||||
output.pushData(`\r\n\r\n\x1b[1m> ${text} <\x1b[0m\r\n\r\n`);
|
||||
output.ended = true;
|
||||
this.revealMarker(terminal, thenSelectMarker);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private revealMarker(terminal: ITerminalInstance, marker?: number) {
|
||||
if (marker !== undefined) {
|
||||
terminal.scrollToMark(getMarkId(marker, true), getMarkId(marker, false), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestOutputProcess extends Disposable implements ITerminalChildProcess {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { URI } from 'vs/base/common/uri';
|
|||
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 { IRichLocation, ISerializedTestResults, ITestItem, ITestMessage, ITestOutputMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestItemExpandState, TestMessageType, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
import { getMarkId, IRichLocation, ISerializedTestResults, ITestItem, ITestMessage, ITestOutputMessage, ITestRunTask, ITestTaskState, ResolvedTestRunRequest, TestItemExpandState, TestMessageType, TestResultItem, TestResultState } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||
import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage';
|
||||
import { maxPriority, statesInOrder, terminalStatePriorities } from 'vs/workbench/contrib/testing/common/testingStates';
|
||||
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
|
||||
|
@ -130,6 +130,7 @@ export const maxCountPriority = (counts: Readonly<TestStateCount>) => {
|
|||
return TestResultState.Unset;
|
||||
};
|
||||
|
||||
const getMarkCode = (marker: number, start: boolean) => `\x1b]633;SetMark;Id=${getMarkId(marker, start)};Hidden\x07`;
|
||||
|
||||
/**
|
||||
* Deals with output of a {@link LiveTestResult}. By default we pass-through
|
||||
|
@ -162,11 +163,19 @@ export class LiveOutputController {
|
|||
/**
|
||||
* Appends data to the output.
|
||||
*/
|
||||
public append(data: VSBuffer): Promise<void> | void {
|
||||
public append(data: VSBuffer, marker?: number): Promise<void> | void {
|
||||
if (this.closed) {
|
||||
return this.closed;
|
||||
}
|
||||
|
||||
if (marker !== undefined) {
|
||||
data = VSBuffer.concat([
|
||||
VSBuffer.fromString(getMarkCode(marker, true)),
|
||||
data,
|
||||
VSBuffer.fromString(getMarkCode(marker, false)),
|
||||
]);
|
||||
}
|
||||
|
||||
this.previouslyWritten?.push(data);
|
||||
this.dataEmitter.fire(data);
|
||||
this._offset += data.byteLength;
|
||||
|
@ -285,6 +294,7 @@ export class LiveTestResult implements ITestResult {
|
|||
private readonly completeEmitter = new Emitter<void>();
|
||||
private readonly changeEmitter = new Emitter<TestResultItemChange>();
|
||||
private readonly testById = new Map<string, TestResultItemWithChildren>();
|
||||
private testMarkerCounter = 0;
|
||||
private _completedAt?: number;
|
||||
|
||||
public readonly onChange = this.changeEmitter.event;
|
||||
|
@ -352,15 +362,24 @@ export class LiveTestResult implements ITestResult {
|
|||
*/
|
||||
public appendOutput(output: VSBuffer, taskId: string, location?: IRichLocation, testId?: string): void {
|
||||
const preview = output.byteLength > 100 ? output.slice(0, 100).toString() + '…' : output.toString();
|
||||
let marker: number | undefined;
|
||||
|
||||
// currently, the UI only exposes jump-to-message from tests or locations,
|
||||
// so no need to mark outputs that don't come from either of those.
|
||||
if (testId || location) {
|
||||
marker = this.testMarkerCounter++;
|
||||
}
|
||||
|
||||
const message: ITestOutputMessage = {
|
||||
location,
|
||||
message: removeAnsiEscapeCodes(preview),
|
||||
offset: this.output.offset,
|
||||
length: output.byteLength,
|
||||
marker: marker,
|
||||
type: TestMessageType.Output,
|
||||
};
|
||||
|
||||
this.output.append(output);
|
||||
this.output.append(output, marker);
|
||||
|
||||
const index = this.mustGetTaskIndex(taskId);
|
||||
if (testId) {
|
||||
|
|
|
@ -163,9 +163,16 @@ export interface ITestOutputMessage {
|
|||
type: TestMessageType.Output;
|
||||
offset: number;
|
||||
length: number;
|
||||
marker?: number;
|
||||
location: IRichLocation | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TTY marker ID for either starting or ending
|
||||
* an ITestOutputMessage.marker of the given ID.
|
||||
*/
|
||||
export const getMarkId = (marker: number, start: boolean) => `${start ? 's' : 'e'}${marker}`;
|
||||
|
||||
export namespace ITestOutputMessage {
|
||||
export interface Serialized {
|
||||
message: string;
|
||||
|
|
Loading…
Reference in a new issue