testing: additional context keys and state around test results (#207119)

For explorations with copilot later
This commit is contained in:
Connor Peet
2024-03-07 19:08:21 -08:00
committed by GitHub
parent 091987d943
commit dcee2edef1
5 changed files with 46 additions and 21 deletions
@@ -74,9 +74,11 @@ export class ExtHostTesting extends Disposable implements ExtHostTestingShape {
return controller?.collection.tree.get(targetTest)?.actual ?? toItemFromContext(arg);
}
case MarshalledId.TestMessageMenuArgs: {
const { extId, message } = arg as ITestMessageMenuArgs;
const { test, message } = arg as ITestMessageMenuArgs;
const extId = test.item.extId;
return {
test: this.controllers.get(TestId.root(extId))?.collection.tree.get(extId)?.actual,
test: this.controllers.get(TestId.root(extId))?.collection.tree.get(extId)?.actual
?? toItemFromContext({ $mid: MarshalledId.TestItemContext, tests: [test] }),
message: Convert.TestMessage.to(message as ITestErrorMessage.Serialized),
};
}
@@ -98,13 +98,19 @@ import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testPro
import { ITaskRawOutput, ITestResult, ITestRunTaskResults, LiveTestResult, TestResultItemChange, TestResultItemChangeReason, maxCountPriority, resultItemParents } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService, ResultChangeEvent } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { IRichLocation, ITestErrorMessage, ITestItem, ITestMessage, ITestMessageMenuArgs, ITestRunTask, ITestTaskState, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId } from 'vs/workbench/contrib/testing/common/testTypes';
import { IRichLocation, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessage, ITestMessageMenuArgs, ITestRunTask, ITestTaskState, InternalTestItem, TestMessageType, TestResultItem, TestResultState, TestRunProfileBitset, getMarkId, testResultStateToContextValues } from 'vs/workbench/contrib/testing/common/testTypes';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { IShowResultOptions, ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { ParsedTestUri, TestUriType, buildTestUri, parseTestUri } from 'vs/workbench/contrib/testing/common/testingUri';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
const getMessageArgs = (test: TestResultItem, message: ITestMessage): ITestMessageMenuArgs => ({
$mid: MarshalledId.TestMessageMenuArgs,
test: InternalTestItem.serialize(test),
message: ITestMessage.serialize(message),
});
class MessageSubject {
public readonly test: ITestItem;
public readonly message: ITestMessage;
@@ -112,6 +118,7 @@ class MessageSubject {
public readonly actualUri: URI;
public readonly messageUri: URI;
public readonly revealLocation: IRichLocation | undefined;
public readonly context: ITestMessageMenuArgs | undefined;
public get isDiffable() {
return this.message.type === TestMessageType.Error && isDiffable(this.message);
@@ -121,14 +128,6 @@ class MessageSubject {
return this.message.type === TestMessageType.Error ? this.message.contextValue : undefined;
}
public get context(): ITestMessageMenuArgs {
return {
$mid: MarshalledId.TestMessageMenuArgs,
extId: this.test.extId,
message: ITestMessage.serialize(this.message),
};
}
constructor(public readonly result: ITestResult, test: TestResultItem, public readonly taskIndex: number, public readonly messageIndex: number) {
this.test = test.item;
const messages = test.tasks[taskIndex].messages;
@@ -140,6 +139,7 @@ class MessageSubject {
this.messageUri = buildTestUri({ ...parts, type: TestUriType.ResultMessage });
const message = this.message = messages[this.messageIndex];
this.context = getMessageArgs(test, message);
this.revealLocation = message.location ?? (test.item.uri && test.item.range ? { uri: test.item.uri, range: Range.lift(test.item.range) } : undefined);
}
}
@@ -1745,7 +1745,10 @@ class CoverageElement implements ITreeElement {
class TestCaseElement implements ITreeElement {
public readonly type = 'test';
public readonly context = this.test.item.extId;
public readonly context: ITestItemContext = {
$mid: MarshalledId.TestItemContext,
tests: [InternalTestItem.serialize(this.test)],
};
public readonly id = `${this.results.id}/${this.test.item.extId}`;
public readonly description?: string;
@@ -1826,13 +1829,12 @@ class TestMessageElement implements ITreeElement {
}
public get context(): ITestMessageMenuArgs {
return {
$mid: MarshalledId.TestMessageMenuArgs,
extId: this.test.item.extId,
message: ITestMessage.serialize(this.message),
};
return getMessageArgs(this.test, this.message);
}
public get outputSubject() {
return new TestOutputSubject(this.result, this.taskIndex, this.test);
}
constructor(
public readonly result: ITestResult,
@@ -2357,11 +2359,10 @@ class TreeActionsProvider {
if (element instanceof TestCaseElement || element instanceof TestMessageElement) {
contextKeys.push(
[TestingContextKeys.testResultOutdated.key, element.test.retired],
[TestingContextKeys.testResultState.key, testResultStateToContextValues[element.test.ownComputedState]],
...getTestItemContextOverlay(element.test, capabilities),
);
}
if (element instanceof TestCaseElement) {
const extId = element.test.item.extId;
if (element.test.tasks[element.taskIndex].messages.some(m => m.type === TestMessageType.Output)) {
primary.push(new Action(
@@ -2401,12 +2402,15 @@ class TreeActionsProvider {
));
}
}
if (element instanceof TestMessageElement) {
primary.push(new Action(
'testing.outputPeek.goToFile',
localize('testing.goToFile', "Go to Source"),
ThemeIcon.asClassName(Codicon.goToFile),
undefined,
() => this.commandService.executeCommand('vscode.revealTest', extId),
() => this.commandService.executeCommand('vscode.revealTest', element.test.item.extId),
));
}
@@ -21,6 +21,16 @@ export const enum TestResultState {
Errored = 6
}
export const testResultStateToContextValues: { [K in TestResultState]: string } = {
[TestResultState.Unset]: 'unset',
[TestResultState.Queued]: 'queued',
[TestResultState.Running]: 'running',
[TestResultState.Passed]: 'passed',
[TestResultState.Failed]: 'failed',
[TestResultState.Skipped]: 'skipped',
[TestResultState.Errored]: 'errored',
};
/** note: keep in sync with TestRunProfileKind in vscode.d.ts */
export const enum ExtTestRunProfileKind {
Run = 1,
@@ -755,7 +765,7 @@ export interface ITestMessageMenuArgs {
/** Marshalling marker */
$mid: MarshalledId.TestMessageMenuArgs;
/** Tests ext ID */
extId: string;
test: InternalTestItem.Serialized;
/** Serialized test message */
message: ITestMessage.Serialized;
}
@@ -67,4 +67,8 @@ export namespace TestingContextKeys {
type: 'boolean',
description: localize('testing.testResultOutdated', 'Value available in editor/content and testing/message/context when the result is outdated')
});
export const testResultState = new RawContextKey<string>('testResultState', undefined, {
type: 'string',
description: localize('testing.testResultState', 'Value available testing/item/result indicating the state of the item.')
});
}
@@ -343,6 +343,11 @@ const apiMenus: IAPIMenu[] = [
id: MenuId.TestItemGutter,
description: localize('testing.item.gutter.title', "The menu for a gutter decoration for a test item"),
},
{
key: 'testing/item/result',
id: MenuId.TestPeekElement,
description: localize('testing.item.result.title', "The menu for an item in the Test Results view or peek."),
},
{
key: 'testing/message/context',
id: MenuId.TestMessageContext,