From 845b291b8cc5fec63b3274065eb487233ed49f54 Mon Sep 17 00:00:00 2001 From: Remi Schnekenburger Date: Wed, 24 Apr 2024 16:30:52 +0200 Subject: [PATCH] Stub TestCoverage API (#13631) contributed on behalf of STMicroelectronics Signed-off-by: Remi Schnekenburger --- CHANGELOG.md | 1 + .../plugin-ext/src/plugin/plugin-context.ts | 14 +- packages/plugin-ext/src/plugin/tests.ts | 5 + packages/plugin-ext/src/plugin/types-impl.ts | 66 ++++++ packages/plugin/src/theia.d.ts | 196 ++++++++++++++++++ 5 files changed, 280 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d3fcdfacf9bc..853ba325270f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ## not yet released - [scm] added support for dirty diff peek view [#13104](https://github.com/eclipse-theia/theia/pull/13104) +- [test] stub VS Code `Test Coverage` API [#13631](https://github.com/eclipse-theia/theia/pull/13631) [Breaking Changes:](#breaking_changes_not_yet_released) - [scm] revised some of the dirty diff related types [#13104](https://github.com/eclipse-theia/theia/pull/13104) diff --git a/packages/plugin-ext/src/plugin/plugin-context.ts b/packages/plugin-ext/src/plugin/plugin-context.ts index 83c3f80a5c312..f405a67633964 100644 --- a/packages/plugin-ext/src/plugin/plugin-context.ts +++ b/packages/plugin-ext/src/plugin/plugin-context.ts @@ -203,7 +203,12 @@ import { TerminalOutputAnchor, TerminalQuickFixTerminalCommand, TerminalQuickFixOpener, - TestResultState + TestResultState, + BranchCoverage, + DeclarationCoverage, + FileCoverage, + StatementCoverage, + TestCoverageCount } from './types-impl'; import { AuthenticationExtImpl } from './authentication-ext'; import { SymbolKind } from '../common/plugin-api-rpc-model'; @@ -1402,7 +1407,12 @@ export function createAPIFactory( TerminalQuickFixTerminalCommand, TerminalQuickFixOpener, EditSessionIdentityMatch, - TestResultState + TestResultState, + BranchCoverage, + DeclarationCoverage, + FileCoverage, + StatementCoverage, + TestCoverageCount }; }; } diff --git a/packages/plugin-ext/src/plugin/tests.ts b/packages/plugin-ext/src/plugin/tests.ts index d6a45a7268aaa..70ae52f38a907 100644 --- a/packages/plugin-ext/src/plugin/tests.ts +++ b/packages/plugin-ext/src/plugin/tests.ts @@ -240,6 +240,8 @@ class TestRun implements theia.TestRun { onDidEnd: Event = this.onDidEndEmitter.event; private onWillFlushEmitter = new Emitter(); onWillFlush: Event = this.onWillFlushEmitter.event; + private onDidDisposeEmitter = new Emitter(); + onDidDispose: Event = this.onDidDisposeEmitter.event; readonly id: string; private testStateDeltas = new Map(); @@ -293,6 +295,9 @@ class TestRun implements theia.TestRun { this.proxy.$notifyTestRunEnded(this.controller.id, this.id); } + /** @stubbed */ + addCoverage(fileCoverage: theia.FileCoverage): void { } + private checkNotEnded(test: theia.TestItem): boolean { if (this.ended) { console.warn(`Setting the state of test "${test.id}" is a no - op after the run ends.`); diff --git a/packages/plugin-ext/src/plugin/types-impl.ts b/packages/plugin-ext/src/plugin/types-impl.ts index 0a0e7c5611407..d46a05d63c4f4 100644 --- a/packages/plugin-ext/src/plugin/types-impl.ts +++ b/packages/plugin-ext/src/plugin/types-impl.ts @@ -3330,6 +3330,72 @@ export class TestMessage implements theia.TestMessage { constructor(public message: string | theia.MarkdownString) { } } +@es5ClassCompat +export class TestCoverageCount { + constructor( public covered: number, public total: number) { } +} + +@es5ClassCompat +export class FileCoverage { + + detailedCoverage?: theia.FileCoverageDetail[]; + + static fromDetails(uri: theia.Uri, details: theia.FileCoverageDetail[]): FileCoverage { + const statements = new TestCoverageCount(0, 0); + const branches = new TestCoverageCount(0, 0); + const decl = new TestCoverageCount(0, 0); + + for (const detail of details) { + if (detail instanceof StatementCoverage) { + statements.total += 1; + statements.covered += detail.executed ? 1 : 0; + + for (const branch of detail.branches) { + branches.total += 1; + branches.covered += branch.executed ? 1 : 0; + } + } else { + decl.total += 1; + decl.covered += detail.executed ? 1 : 0; + } + } + + const coverage = new FileCoverage( + uri, + statements, + branches.total > 0 ? branches : undefined, + decl.total > 0 ? decl : undefined, + ); + + coverage.detailedCoverage = details; + + return coverage; + } + + constructor( + public uri: theia.Uri, + public statementCoverage: TestCoverageCount, + public branchCoverage?: TestCoverageCount, + public declarationCoverage?: TestCoverageCount, + ) { } +} + +@es5ClassCompat +export class StatementCoverage implements theia.StatementCoverage { + constructor(public executed: number | boolean, public location: Position | Range, public branches: BranchCoverage[] = []) { } +} + +export class BranchCoverage implements theia.BranchCoverage { + constructor(public executed: number | boolean, public location?: Position | Range, public label?: string) { } +} + +@es5ClassCompat +export class DeclarationCoverage implements theia.DeclarationCoverage { + constructor(public name: string, public executed: number | boolean, public location: Position | Range) { } +} + +export type FileCoverageDetail = StatementCoverage | DeclarationCoverage; + @es5ClassCompat export class TimelineItem { timestamp: number; diff --git a/packages/plugin/src/theia.d.ts b/packages/plugin/src/theia.d.ts index e8015f3e5cce4..3ffce743ddb84 100644 --- a/packages/plugin/src/theia.d.ts +++ b/packages/plugin/src/theia.d.ts @@ -16035,6 +16035,18 @@ export module '@theia/plugin' { */ runHandler: (request: TestRunRequest, token: CancellationToken) => Thenable | void; + /** + * An extension-provided function that provides detailed statement and + * function-level coverage for a file. The editor will call this when more + * detail is needed for a file, such as when it's opened in an editor or + * expanded in the **Test Coverage** view. + * + * The {@link FileCoverage} object passed to this function is the same instance + * emitted on {@link TestRun.addCoverage} calls associated with this profile. + * @stubbed + */ + loadDetailedCoverage?: (testRun: TestRun, fileCoverage: FileCoverage, token: CancellationToken) => Thenable; + /** * Deletes the run profile. */ @@ -16314,11 +16326,23 @@ export module '@theia/plugin' { */ appendOutput(output: string, location?: Location, test?: TestItem): void; + /** + * Adds coverage for a file in the run. + * @stubbed + */ + addCoverage(fileCoverage: FileCoverage): void; + /** * Signals the end of the test run. Any tests included in the run whose * states have not been updated will have their state reset. */ end(): void; + + /** + * An event fired when the editor is no longer interested in data + * associated with the test run. + */ + onDidDispose: Event; } /** @@ -16527,6 +16551,178 @@ export module '@theia/plugin' { */ constructor(message: string | MarkdownString); } + + /** + * A class that contains information about a covered resource. A count can + * be give for lines, branches, and declarations in a file. + */ + export class TestCoverageCount { + /** + * Number of items covered in the file. + */ + covered: number; + /** + * Total number of covered items in the file. + */ + total: number; + + /** + * @param covered Value for {@link TestCoverageCount.covered} + * @param total Value for {@link TestCoverageCount.total} + */ + constructor(covered: number, total: number); + } + + /** + * Contains coverage metadata for a file. + */ + export class FileCoverage { + /** + * File URI. + */ + readonly uri: Uri; + + /** + * Statement coverage information. If the reporter does not provide statement + * coverage information, this can instead be used to represent line coverage. + */ + statementCoverage: TestCoverageCount; + + /** + * Branch coverage information. + */ + branchCoverage?: TestCoverageCount; + + /** + * Declaration coverage information. Depending on the reporter and + * language, this may be types such as functions, methods, or namespaces. + */ + declarationCoverage?: TestCoverageCount; + + /** + * Creates a {@link FileCoverage} instance with counts filled in from + * the coverage details. + * @param uri Covered file URI + * @param detailed Detailed coverage information + */ + static fromDetails(uri: Uri, details: readonly FileCoverageDetail[]): FileCoverage; + + /** + * @param uri Covered file URI + * @param statementCoverage Statement coverage information. If the reporter + * does not provide statement coverage information, this can instead be + * used to represent line coverage. + * @param branchCoverage Branch coverage information + * @param declarationCoverage Declaration coverage information + */ + constructor( + uri: Uri, + statementCoverage: TestCoverageCount, + branchCoverage?: TestCoverageCount, + declarationCoverage?: TestCoverageCount, + ); + } + + /** + * Contains coverage information for a single statement or line. + */ + export class StatementCoverage { + /** + * The number of times this statement was executed, or a boolean indicating + * whether it was executed if the exact count is unknown. If zero or false, + * the statement will be marked as un-covered. + */ + executed: number | boolean; + + /** + * Statement location. + */ + location: Position | Range; + + /** + * Coverage from branches of this line or statement. If it's not a + * conditional, this will be empty. + */ + branches: BranchCoverage[]; + + /** + * @param location The statement position. + * @param executed The number of times this statement was executed, or a + * boolean indicating whether it was executed if the exact count is + * unknown. If zero or false, the statement will be marked as un-covered. + * @param branches Coverage from branches of this line. If it's not a + * conditional, this should be omitted. + */ + constructor(executed: number | boolean, location: Position | Range, branches?: BranchCoverage[]); + } + + /** + * Contains coverage information for a branch of a {@link StatementCoverage}. + */ + export class BranchCoverage { + /** + * The number of times this branch was executed, or a boolean indicating + * whether it was executed if the exact count is unknown. If zero or false, + * the branch will be marked as un-covered. + */ + executed: number | boolean; + + /** + * Branch location. + */ + location?: Position | Range; + + /** + * Label for the branch, used in the context of "the ${label} branch was + * not taken," for example. + */ + label?: string; + + /** + * @param executed The number of times this branch was executed, or a + * boolean indicating whether it was executed if the exact count is + * unknown. If zero or false, the branch will be marked as un-covered. + * @param location The branch position. + */ + constructor(executed: number | boolean, location?: Position | Range, label?: string); + } + + /** + * Contains coverage information for a declaration. Depending on the reporter + * and language, this may be types such as functions, methods, or namespaces. + */ + export class DeclarationCoverage { + /** + * Name of the declaration. + */ + name: string; + + /** + * The number of times this declaration was executed, or a boolean + * indicating whether it was executed if the exact count is unknown. If + * zero or false, the declaration will be marked as un-covered. + */ + executed: number | boolean; + + /** + * Declaration location. + */ + location: Position | Range; + + /** + * @param executed The number of times this declaration was executed, or a + * boolean indicating whether it was executed if the exact count is + * unknown. If zero or false, the declaration will be marked as un-covered. + * @param location The declaration position. + */ + constructor(name: string, executed: number | boolean, location: Position | Range); + } + + /** + * Coverage details returned from {@link TestRunProfile.loadDetailedCoverage}. + */ + export type FileCoverageDetail = StatementCoverage | DeclarationCoverage; + /** * Thenable is a common denominator between ES6 promises, Q, jquery.Deferred, WinJS.Promise, * and others. This API makes no assumption about what promise library is being used which