Skip to content

Commit

Permalink
Support TestMessageStackFrame API (#14154)
Browse files Browse the repository at this point in the history
Support TestMessageStackFrame API

fixes #14111

contributed on behalf of STMicroelectronics

Signed-off-by: Remi Schnekenburger <rschnekenburger@eclipsesource.com>
  • Loading branch information
rschnekenbu authored Sep 23, 2024
1 parent dedfe25 commit 88b64b2
Show file tree
Hide file tree
Showing 14 changed files with 222 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- [plugin] move stubbed API TerminalShellIntegration into main API [#14168](https://github.com/eclipse-theia/theia/pull/14168) - Contributed on behalf of STMicroelectronics
- [plugin] support evolution on proposed API extensionAny [#14199](https://github.com/eclipse-theia/theia/pull/14199) - Contributed on behalf of STMicroelectronics
- [test] support TestMessage stack traces [#14154](https://github.com/eclipse-theia/theia/pull/14154) - Contributed on behalf of STMicroelectronics
<a name="breaking_changes_1.54.0">[Breaking Changes:](#breaking_changes_1.54.0)</a> -->
- [core] Updated AuthenticationService to handle multiple accounts per provider [#14149](https://github.com/eclipse-theia/theia/pull/14149) - Contributed on behalf of STMicroelectronics
Expand Down
11 changes: 11 additions & 0 deletions packages/plugin-ext/src/common/plugin-api-rpc-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ export interface Range {
readonly endColumn: number;
}

export interface Position {
/**
* line number (starts at 1)
*/
readonly lineNumber: number,
/**
* column (starts at 1)
*/
readonly column: number
}

export { MarkdownStringDTO as MarkdownString };

export interface SerializedDocumentFilter {
Expand Down
16 changes: 15 additions & 1 deletion packages/plugin-ext/src/common/test-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
import { UriComponents } from './uri-components';
import { Location, Range } from './plugin-api-rpc-model';
import { isObject } from '@theia/core';
import * as languageProtocol from '@theia/core/shared/vscode-languageserver-protocol';

export enum TestRunProfileKind {
Run = 1,
Expand Down Expand Up @@ -74,17 +75,30 @@ export interface TestFailureDTO extends TestStateChangeDTO {
readonly duration?: number;
}

export namespace TestFailureDTO {
export function is(ref: unknown): ref is TestFailureDTO {
return isObject<TestFailureDTO>(ref)
&& (ref.state === TestExecutionState.Failed || ref.state === TestExecutionState.Errored);
}
}
export interface TestSuccessDTO extends TestStateChangeDTO {
readonly state: TestExecutionState.Passed;
readonly duration?: number;
}

export interface TestMessageStackFrameDTO {
uri?: languageProtocol.DocumentUri;
position?: languageProtocol.Position;
label: string;
}

export interface TestMessageDTO {
readonly expected?: string;
readonly actual?: string;
readonly location?: Location;
readonly location?: languageProtocol.Location;
readonly message: string | MarkdownString;
readonly contextValue?: string;
readonly stackTrace?: TestMessageStackFrameDTO[];
}

export interface TestItemDTO {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { TreeViewWidget } from '../view/tree-view-widget';
import { CodeEditorWidgetUtil, codeToTheiaMappings, ContributionPoint } from './vscode-theia-menu-mappings';
import { TAB_BAR_TOOLBAR_CONTEXT_MENU } from '@theia/core/lib/browser/shell/tab-bar-toolbar';
import { TestItem, TestMessage } from '@theia/test/lib/browser/test-service';
import { fromLocation } from '../hierarchy/hierarchy-types-converters';

export type ArgumentAdapter = (...args: unknown[]) => unknown[];

Expand Down Expand Up @@ -315,7 +314,8 @@ export class PluginMenuCommandAdapter implements MenuCommandAdapter {
actual: testMessage.actual,
expected: testMessage.expected,
contextValue: testMessage.contextValue,
location: testMessage.location ? fromLocation(testMessage.location) : undefined
location: testMessage.location,
stackTrace: testMessage.stackTrace
};
return [TestMessageArg.create(testItemReference, testMessageDTO)];
}
Expand Down
5 changes: 4 additions & 1 deletion packages/plugin-ext/src/main/browser/test-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ import { CancellationToken, Disposable, Event, URI } from '@theia/core';
import { MAIN_RPC_CONTEXT, TestControllerUpdate, TestingExt, TestingMain } from '../../common';
import { RPCProtocol } from '../../common/rpc-protocol';
import { interfaces } from '@theia/core/shared/inversify';
import { TestExecutionState, TestItemDTO, TestItemReference, TestOutputDTO, TestRunDTO, TestRunProfileDTO, TestStateChangeDTO } from '../../common/test-types';
import {
TestExecutionState, TestItemDTO, TestItemReference, TestOutputDTO,
TestRunDTO, TestRunProfileDTO, TestStateChangeDTO
} from '../../common/test-types';
import { TestRunProfileKind } from '../../plugin/types-impl';
import { CommandRegistryMainImpl } from './command-registry-main';

Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/plugin/plugin-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ import {
TestTag,
TestRunRequest,
TestMessage,
TestMessageStackFrame,
ExtensionKind,
InlineCompletionItem,
InlineCompletionList,
Expand Down Expand Up @@ -1463,6 +1464,7 @@ export function createAPIFactory(
TestTag,
TestRunRequest,
TestMessage,
TestMessageStackFrame,
ExtensionKind,
InlineCompletionItem,
InlineCompletionList,
Expand Down
37 changes: 34 additions & 3 deletions packages/plugin-ext/src/plugin/tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ import { TestItemImpl, TestItemCollection } from './test-item';
import { AccumulatingTreeDeltaEmitter, TreeDelta } from '@theia/test/lib/common/tree-delta';
import {
TestItemDTO, TestOutputDTO, TestExecutionState, TestRunProfileDTO,
TestRunProfileKind, TestRunRequestDTO, TestStateChangeDTO, TestItemReference, TestMessageArg, TestMessageDTO
TestRunProfileKind, TestRunRequestDTO, TestStateChangeDTO, TestItemReference, TestMessageArg, TestMessageDTO,
TestMessageStackFrameDTO
} from '../common/test-types';
import * as protocol from '@theia/core/shared/vscode-languageserver-protocol';
import { ChangeBatcher, observableProperty } from '@theia/test/lib/common/collections';
import { TestRunRequest } from './types-impl';
import { Location, Position, Range, TestRunRequest, URI } from './types-impl';
import { MarkdownString } from '../common/plugin-api-rpc-model';

type RefreshHandler = (token: theia.CancellationToken) => void | theia.Thenable<void>;
Expand Down Expand Up @@ -374,7 +376,36 @@ export class TestingExtImpl implements TestingExt {
actualOutput: testMessage.actual,
expectedOutput: testMessage.expected,
contextValue: testMessage.contextValue,
location: testMessage.location ? Convert.toLocation(testMessage.location) : undefined
location: this.toLocation(testMessage.location),
stackTrace: testMessage.stackTrace ? testMessage.stackTrace.map(frame => this.toStackFrame(frame)) : undefined
};
}

toLocation(location: protocol.Location | undefined): Location | undefined {
if (!location) {
return undefined;
}
return new Location(URI.parse(location.uri), this.toRange(location.range));
}

toRange(range: protocol.Range): Range {
return new Range(this.toPosition(range.start), this.toPosition(range.end));
}

toPosition(position: protocol.Position): Position;
toPosition(position: protocol.Position | undefined): Position | undefined;
toPosition(position: protocol.Position | undefined): Position | undefined {
if (!position) {
return undefined;
}
return new Position(position.line, position.character);
}

toStackFrame(stackFrame: TestMessageStackFrameDTO): theia.TestMessageStackFrame {
return {
label: stackFrame.label,
position: this.toPosition(stackFrame.position),
uri: stackFrame.uri ? URI.parse(stackFrame.uri) : undefined
};
}

Expand Down
44 changes: 38 additions & 6 deletions packages/plugin-ext/src/plugin/type-converters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { BinaryBuffer } from '@theia/core/lib/common/buffer';
import { CellRange, isTextStreamMime } from '@theia/notebook/lib/common';
import { MarkdownString as MarkdownStringDTO } from '@theia/core/lib/common/markdown-rendering';

import { TestItemDTO, TestMessageDTO } from '../common/test-types';
import { TestItemDTO, TestMessageDTO, TestMessageStackFrameDTO } from '../common/test-types';
import { PluginIconPath } from './plugin-icon-path';

const SIDE_GROUP = -2;
Expand Down Expand Up @@ -134,12 +134,21 @@ export function fromRange(range: theia.Range | undefined): model.Range | undefin
endColumn: end.character + 1
};
}

export function fromPosition(position: types.Position | theia.Position): Position {
export function fromPosition(position: types.Position | theia.Position): Position;
export function fromPosition(position: types.Position | theia.Position | undefined): Position | undefined;
export function fromPosition(position: types.Position | theia.Position | undefined): Position | undefined {
if (!position) {
return undefined;
}
return { lineNumber: position.line + 1, column: position.character + 1 };
}

export function toPosition(position: Position): types.Position {
export function toPosition(position: Position): types.Position;
export function toPosition(position: Position | undefined): types.Position | undefined;
export function toPosition(position: Position | undefined): types.Position | undefined {
if (!position) {
return undefined;
}
return new types.Position(position.lineNumber - 1, position.column - 1);
}

Expand Down Expand Up @@ -474,6 +483,18 @@ export function fromLocation(location: theia.Location | undefined): model.Locati
};
}

export function fromLocationToLanguageServerLocation(location: theia.Location): lstypes.Location;
export function fromLocationToLanguageServerLocation(location: theia.Location | undefined): lstypes.Location | undefined;
export function fromLocationToLanguageServerLocation(location: theia.Location | undefined): lstypes.Location | undefined {
if (!location) {
return undefined;
}
return <lstypes.Location>{
uri: location.uri.toString(),
range: location.range
};
}

export function fromTextDocumentShowOptions(options: theia.TextDocumentShowOptions): model.TextDocumentShowOptions {
if (options.selection) {
return {
Expand Down Expand Up @@ -1697,15 +1718,26 @@ export namespace TestMessage {
return message.map(msg => TestMessage.from(msg)[0]);
}
return [{
location: fromLocation(message.location),
location: fromLocationToLanguageServerLocation(message.location),
message: fromMarkdown(message.message)!,
expected: message.expectedOutput,
actual: message.actualOutput,
contextValue: message.contextValue
contextValue: message.contextValue,
stackTrace: message.stackTrace && message.stackTrace.map(frame => TestMessageStackFrame.from(frame))
}];
}
}

export namespace TestMessageStackFrame {
export function from(stackTrace: theia.TestMessageStackFrame): TestMessageStackFrameDTO {
return {
label: stackTrace.label,
position: stackTrace.position,
uri: stackTrace?.uri?.toString()
};
}
}

export namespace TestItem {
export function from(test: theia.TestItem): TestItemDTO {
return <TestItemDTO>TestItem.fromPartial(test);
Expand Down
11 changes: 10 additions & 1 deletion packages/plugin-ext/src/plugin/types-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3036,7 +3036,7 @@ export class DebugThread implements theia.DebugThread {
}

export class DebugStackFrame implements theia.DebugStackFrame {
constructor(readonly session: theia.DebugSession, readonly threadId: number, readonly frameId: number) { }
constructor(readonly session: theia.DebugSession, readonly threadId: number, readonly frameId: number) { }
}

@es5ClassCompat
Expand Down Expand Up @@ -3350,6 +3350,7 @@ export class TestMessage implements theia.TestMessage {
public actualOutput?: string;
public location?: theia.Location;
public contextValue?: string;
public stackTrace?: theia.TestMessageStackFrame[] | undefined;

public static diff(message: string | theia.MarkdownString, expected: string, actual: string): theia.TestMessage {
const msg = new TestMessage(message);
Expand All @@ -3366,6 +3367,14 @@ export class TestCoverageCount {
constructor(public covered: number, public total: number) { }
}

export class TestMessageStackFrame implements theia.TestMessageStackFrame {
constructor(
public label: string,
public uri?: theia.Uri,
public position?: Position
) { }
}

@es5ClassCompat
export class FileCoverage {

Expand Down
33 changes: 33 additions & 0 deletions packages/plugin/src/theia.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16966,6 +16966,34 @@ export module '@theia/plugin' {
error: string | MarkdownString | undefined;
}

/**
* A stack frame found in the {@link TestMessage.stackTrace}.
*/
export class TestMessageStackFrame {
/**
* The location of this stack frame. This should be provided as a URI if the
* location of the call frame can be accessed by the editor.
*/
uri?: Uri;

/**
* Position of the stack frame within the file.
*/
position?: Position;

/**
* The name of the stack frame, typically a method or function name.
*/
label: string;

/**
* @param label The name of the stack frame
* @param file The file URI of the stack frame
* @param position The position of the stack frame within the file
*/
constructor(label: string, uri?: Uri, position?: Position);
}

/**
* Message associated with the test state. Can be linked to a specific
* source range -- useful for assertion failures, for example.
Expand Down Expand Up @@ -17022,6 +17050,11 @@ export module '@theia/plugin' {
*/
contextValue?: string;

/**
* The stack trace associated with the message or failure.
*/
stackTrace?: TestMessageStackFrame[];

/**
* Creates a new TestMessage that will present as a diff in the editor.
* @param message Message to display to the user.
Expand Down
12 changes: 8 additions & 4 deletions packages/test/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,29 @@
}

.theia-test-view .passed,
.theia-test-result-view .passed {
.theia-test-run-view .passed {
color: var(--theia-successBackground);
}

.theia-test-view .failed,
.theia-test-result-view .failed {
.theia-test-run-view .failed {
color: var(--theia-editorError-foreground);
}

.theia-test-view .errored,
.theia-test-result-view .errored {
.theia-test-run-view .errored {
color: var(--theia-editorError-foreground);
}

.theia-test-view .queued,
.theia-test-result-view .queued {
.theia-test-run-view .queued {
color: var(--theia-editorWarning-foreground);
}

.theia-test-result-view .debug-frame {
white-space: pre;
}

.theia-test-view .theia-TreeNode:not(:hover):not(.theia-mod-selected) .theia-test-tree-inline-action {
display: none;
}
13 changes: 10 additions & 3 deletions packages/test/src/browser/test-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// *****************************************************************************

import { CancellationToken, ContributionProvider, Disposable, Emitter, Event, QuickPickService, isObject, nls } from '@theia/core/lib/common';
import { CancellationTokenSource, Location, Range } from '@theia/core/shared/vscode-languageserver-protocol';
import { CancellationTokenSource, Location, Range, Position, DocumentUri } from '@theia/core/shared/vscode-languageserver-protocol';
import { CollectionDelta, TreeDelta } from '../common/tree-delta';
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
import URI from '@theia/core/lib/common/uri';
Expand Down Expand Up @@ -56,9 +56,16 @@ export enum TestExecutionState {
export interface TestMessage {
readonly expected?: string;
readonly actual?: string;
readonly location: Location;
readonly location?: Location;
readonly message: string | MarkdownString;
readonly contextValue?: string;
readonly stackTrace?: TestMessageStackFrame[];
}

export interface TestMessageStackFrame {
readonly label: string,
readonly uri?: DocumentUri,
readonly position?: Position,
}

export namespace TestMessage {
Expand Down Expand Up @@ -367,7 +374,7 @@ export class DefaultTestService implements TestService {

selectDefaultProfile(): void {
this.pickProfileKind().then(kind => {
const profiles = this.getControllers().flatMap(c => c.testRunProfiles).filter(profile => profile.kind === kind);
const profiles = this.getControllers().flatMap(c => c.testRunProfiles).filter(profile => profile.kind === kind);
this.pickProfile(profiles, nls.localizeByDefault('Pick a test profile to use')).then(activeProfile => {
if (activeProfile) {
// only change the default for the controller containing selected profile for default and its profiles with same kind
Expand Down
Loading

0 comments on commit 88b64b2

Please sign in to comment.