Skip to content

Commit

Permalink
Resolve test items with server request (#3186)
Browse files Browse the repository at this point in the history
* Add discover tests custom request (#3180)

### Motivation

Closes #3171

Add a new custom request to discover tests in a specific document. This request will instantiate all listeners and collect all of the discovered groups and examples as test items for the editor.

### Implementation

- Created the new request
- Implement a listener dedicated to the test style syntax only (spec will be a separate listener)
- Ensured to use ancestor linearization to determine the test framework

### Automated Tests

Added tests.

* Resolve test items with server request (#3186)

### Motivation

Closes #3173

Start firing our new custom request to discover which tests are available in a given file.

### Implementation

We fire the request and then recursively add test items to their respective collections. If a document is saved, we check to see if we created a test item for it and then we clear its children (the tests defined inside of the file) and re-trigger the request.

I chose to use on save because on change would put a lot of pressure on the server.

### Automated Tests

Added tests.

---------

Co-authored-by: vinistock <[email protected]>
  • Loading branch information
vinistock and vinistock authored Feb 13, 2025
1 parent 7db4a9f commit e02a704
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 5 deletions.
17 changes: 17 additions & 0 deletions vscode/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ import { WorkspaceChannel } from "./workspaceChannel";

type EnabledFeatures = Record<string, boolean>;

export interface ServerTestItem {
id: string;
label: string;
uri: string;
range: {
start: { line: number; column: number };
end: { line: number; column: number };
};
children: ServerTestItem[];
}

interface ServerErrorTelemetryEvent {
type: "error";
errorMessage: string;
Expand Down Expand Up @@ -459,6 +470,12 @@ export default class Client extends LanguageClient implements ClientInterface {
});
}

async discoverTests(uri: vscode.Uri): Promise<ServerTestItem[]> {
return this.sendRequest("rubyLsp/discoverTests", {
textDocument: { uri: uri.toString() },
});
}

async dispose(timeout?: number): Promise<void> {
this.subscriptions.forEach((subscription) => subscription.dispose());
this.subscriptions = [];
Expand Down
37 changes: 36 additions & 1 deletion vscode/src/rubyLsp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class RubyLsp {
context,
this.telemetry,
this.currentActiveWorkspace.bind(this),
this.getOrActivateWorkspace.bind(this),
);
this.debug = new Debugger(context, this.workspaceResolver.bind(this));
this.rails = new Rails(this.showWorkspacePick.bind(this));
Expand Down Expand Up @@ -174,10 +175,22 @@ export class RubyLsp {
}
}

// Overloaded signatures because when the workspace activation is lazy, it is guaranteed to return `Workspace`, which
// avoids checking for undefined in the caller
private async activateWorkspace(
workspaceFolder: vscode.WorkspaceFolder,
eager: true,
): Promise<Workspace | undefined>;

private async activateWorkspace(
workspaceFolder: vscode.WorkspaceFolder,
eager: false,
): Promise<Workspace>;

private async activateWorkspace(
workspaceFolder: vscode.WorkspaceFolder,
eager: boolean,
) {
): Promise<Workspace | undefined> {
const customBundleGemfile: string = vscode.workspace
.getConfiguration("rubyLsp")
.get("bundleGemfile")!;
Expand Down Expand Up @@ -219,6 +232,7 @@ export class RubyLsp {
);
await this.showFormatOnSaveModeWarning(workspace);
this.workspacesBeingLaunched.delete(workspaceFolder.index);
return workspace;
}

// Registers all extension commands. Commands can only be registered once, so this happens in the constructor. For
Expand Down Expand Up @@ -702,6 +716,27 @@ export class RubyLsp {
return this.getWorkspace(workspaceFolder.uri);
}

private async getOrActivateWorkspace(
workspaceFolder: vscode.WorkspaceFolder,
): Promise<Workspace> {
const workspace = this.getWorkspace(workspaceFolder.uri);

if (workspace) {
return workspace;
}

return vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: `Workspace ${workspaceFolder.name} is not activated yet.`,
},
async (progress) => {
progress.report({ message: "Activating workspace..." });
return this.activateWorkspace(workspaceFolder, false);
},
);
}

private getWorkspace(uri: vscode.Uri): Workspace | undefined {
return this.workspaces.get(uri.toString());
}
Expand Down
100 changes: 100 additions & 0 deletions vscode/src/test/suite/testController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import sinon from "sinon";

import { TestController } from "../../testController";
import * as common from "../../common";
import { Workspace } from "../../workspace";

import { FAKE_TELEMETRY } from "./fakeTelemetry";

Expand All @@ -31,6 +32,14 @@ suite("TestController", () => {
update: (_name: string, _value: any) => Promise.resolve(),
},
} as unknown as vscode.ExtensionContext;
const workspace = new Workspace(
context,
workspaceFolder,
FAKE_TELEMETRY,
() => undefined,
new Map(),
true,
);

afterEach(() => {
context.subscriptions.forEach((subscription) => subscription.dispose());
Expand All @@ -41,6 +50,7 @@ suite("TestController", () => {
context,
FAKE_TELEMETRY,
() => undefined,
() => Promise.resolve(workspace),
);

const codeLensItems: CodeLens[] = [
Expand Down Expand Up @@ -83,6 +93,7 @@ suite("TestController", () => {
context,
FAKE_TELEMETRY,
() => undefined,
() => Promise.resolve(workspace),
);
stub.restore();

Expand All @@ -106,11 +117,19 @@ suite("TestController", () => {
vscode.Uri.joinPath(workspaceUri, "test").toString(),
);
assert.ok(testDir);
assert.deepStrictEqual(
testDir!.tags.map((tag) => tag.id),
["test_dir", "debug"],
);

const serverTest = testDir!.children.get(
vscode.Uri.joinPath(workspaceUri, "test", "server_test.rb").toString(),
);
assert.ok(serverTest);
assert.deepStrictEqual(
serverTest!.tags.map((tag) => tag.id),
["debug"],
);
});

test("makes the workspaces the top level when there's more than one", async () => {
Expand All @@ -119,6 +138,7 @@ suite("TestController", () => {
context,
FAKE_TELEMETRY,
() => undefined,
() => Promise.resolve(workspace),
);
stub.restore();

Expand Down Expand Up @@ -171,27 +191,49 @@ suite("TestController", () => {
// First workspace
const workspaceItem = collection.get(workspaceUri.toString());
assert.ok(workspaceItem);
assert.deepStrictEqual(
workspaceItem!.tags.map((tag) => tag.id),
["workspace", "debug"],
);

await controller.testController.resolveHandler!(workspaceItem);

const testDir = workspaceItem!.children.get(
vscode.Uri.joinPath(workspaceUri, "test").toString(),
);
assert.ok(testDir);
assert.deepStrictEqual(
testDir!.tags.map((tag) => tag.id),
["test_dir", "debug"],
);

const serverTest = testDir!.children.get(
vscode.Uri.joinPath(workspaceUri, "test", "server_test.rb").toString(),
);
assert.ok(serverTest);
assert.deepStrictEqual(
serverTest!.tags.map((tag) => tag.id),
["debug"],
);

// Second workspace
const secondWorkspaceItem = collection.get(secondWorkspaceUri.toString());
assert.ok(secondWorkspaceItem);
assert.deepStrictEqual(
secondWorkspaceItem!.tags.map((tag) => tag.id),
["workspace", "debug"],
);

await controller.testController.resolveHandler!(secondWorkspaceItem);

const secondTestDir = secondWorkspaceItem!.children.get(
vscode.Uri.joinPath(secondWorkspaceUri, "test").toString(),
);
assert.ok(secondTestDir);
assert.deepStrictEqual(
secondTestDir!.tags.map((tag) => tag.id),
["test_dir", "debug"],
);

const otherTest = secondTestDir!.children.get(
vscode.Uri.joinPath(
Expand All @@ -201,8 +243,66 @@ suite("TestController", () => {
).toString(),
);
assert.ok(otherTest);
assert.deepStrictEqual(
otherTest!.tags.map((tag) => tag.id),
["debug"],
);

workspacesStub.restore();
relativePathStub.restore();
getWorkspaceStub.restore();
});

test("fires discover tests request when resolving a specific test file", async () => {
const stub = sinon.stub(common, "featureEnabled").returns(true);
const controller = new TestController(
context,
FAKE_TELEMETRY,
() => undefined,
() => Promise.resolve(workspace),
);
stub.restore();

const workspacesStub = sinon
.stub(vscode.workspace, "workspaceFolders")
.get(() => [workspaceFolder]);

const relativePathStub = sinon
.stub(vscode.workspace, "asRelativePath")
.callsFake((uri) =>
path.relative(workspacePath, (uri as vscode.Uri).fsPath),
);

await controller.testController.resolveHandler!(undefined);

const collection = controller.testController.items;

const testDir = collection.get(
vscode.Uri.joinPath(workspaceUri, "test").toString(),
);
assert.ok(testDir);
assert.deepStrictEqual(
testDir!.tags.map((tag) => tag.id),
["test_dir", "debug"],
);

const serverTest = testDir!.children.get(
vscode.Uri.joinPath(workspaceUri, "test", "server_test.rb").toString(),
);
assert.ok(serverTest);
assert.deepStrictEqual(
serverTest!.tags.map((tag) => tag.id),
["debug"],
);

const fakeClient = {
discoverTests: sinon.stub().resolves([]),
};
workspace.lspClient = fakeClient as any;
await controller.testController.resolveHandler!(serverTest);
assert.strictEqual(fakeClient.discoverTests.callCount, 1);

workspacesStub.restore();
relativePathStub.restore();
});
});
Loading

0 comments on commit e02a704

Please sign in to comment.