Skip to content

Commit a38fdb4

Browse files
committed
Add debugger integration test for project with local Bundler settings
1 parent 460743e commit a38fdb4

File tree

6 files changed

+251
-62
lines changed

6 files changed

+251
-62
lines changed

vscode/src/debugger.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ export class Debugger
3434
uri: vscode.Uri | undefined,
3535
) => Workspace | undefined;
3636

37+
private readonly context: vscode.ExtensionContext;
38+
3739
constructor(
3840
context: vscode.ExtensionContext,
3941
workspaceResolver: (uri: vscode.Uri | undefined) => Workspace | undefined,
4042
) {
4143
this.workspaceResolver = workspaceResolver;
4244

45+
this.context = context;
4346
context.subscriptions.push(
4447
vscode.debug.registerDebugConfigurationProvider("ruby_lsp", this),
4548
vscode.debug.registerDebugAdapterDescriptorFactory("ruby_lsp", this),
@@ -258,9 +261,6 @@ export class Debugger
258261

259262
this.logDebuggerMessage(`Spawning debugger in directory ${cwd}`);
260263
this.logDebuggerMessage(` Command bundle ${args.join(" ")}`);
261-
this.logDebuggerMessage(
262-
` Environment ${JSON.stringify(configuration.env)}`,
263-
);
264264

265265
this.debugProcess = spawn("bundle", args, {
266266
shell: true,
@@ -354,5 +354,10 @@ export class Debugger
354354
// Log to Debug Console: Unlike Output panel, this needs explicit newlines
355355
// so we preserve the original message format including any newlines
356356
this.console.append(message);
357+
358+
if (this.context.extensionMode === vscode.ExtensionMode.Test) {
359+
// eslint-disable-next-line no-console
360+
console.log(message);
361+
}
357362
}
358363
}

vscode/src/rubyLsp.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ import { collectRubyLspInfo } from "./infoCollector";
2323
// activation event. One instance of this class controls all of the existing workspaces, telemetry and handles all
2424
// commands
2525
export class RubyLsp {
26-
private readonly workspaces: Map<string, Workspace> = new Map();
26+
// Only public for testing
27+
public readonly workspaces: Map<string, Workspace> = new Map();
2728
private readonly context: vscode.ExtensionContext;
2829
private readonly statusItems: StatusItems;
2930
private readonly testController: TestController;
@@ -119,10 +120,10 @@ export class RubyLsp {
119120

120121
// Activate the extension. This method should perform all actions necessary to start the extension, such as booting
121122
// all language servers for each existing workspace
122-
async activate() {
123-
await vscode.commands.executeCommand("testing.clearTestResults");
124-
125-
const firstWorkspace = vscode.workspace.workspaceFolders?.[0];
123+
async activate(firstWorkspace = vscode.workspace.workspaceFolders?.[0]) {
124+
if (this.context.extensionMode !== vscode.ExtensionMode.Test) {
125+
await vscode.commands.executeCommand("testing.clearTestResults");
126+
}
126127

127128
// We only activate the first workspace eagerly to avoid running into performance and memory issues. Having too many
128129
// workspaces spawning the Ruby LSP server and indexing can grind the editor to a halt. All other workspaces are

vscode/src/test/suite/client.test.ts

+4-54
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import {
2828
} from "vscode-languageclient/node";
2929
import { after, afterEach, before } from "mocha";
3030

31-
import { Ruby, ManagerIdentifier } from "../../ruby";
31+
import { Ruby } from "../../ruby";
3232
import Client from "../../client";
3333
import { WorkspaceChannel } from "../../workspaceChannel";
34-
import { RUBY_VERSION, MAJOR, MINOR } from "../rubyVersion";
34+
import { MAJOR, MINOR } from "../rubyVersion";
3535

3636
import { FAKE_TELEMETRY } from "./fakeTelemetry";
37+
import { ensureRubyInstallationPaths } from "./testHelpers";
3738

3839
class FakeLogger {
3940
receivedMessages = "";
@@ -85,58 +86,7 @@ async function launchClient(workspaceUri: vscode.Uri) {
8586
const fakeLogger = new FakeLogger();
8687
const outputChannel = new WorkspaceChannel("fake", fakeLogger as any);
8788

88-
// Ensure that we're activating the correct Ruby version on CI
89-
if (process.env.CI) {
90-
if (os.platform() === "linux") {
91-
await vscode.workspace
92-
.getConfiguration("rubyLsp")
93-
.update(
94-
"rubyVersionManager",
95-
{ identifier: ManagerIdentifier.Chruby },
96-
true,
97-
);
98-
99-
fs.mkdirSync(path.join(os.homedir(), ".rubies"), { recursive: true });
100-
fs.symlinkSync(
101-
`/opt/hostedtoolcache/Ruby/${RUBY_VERSION}/x64`,
102-
path.join(os.homedir(), ".rubies", RUBY_VERSION),
103-
);
104-
} else if (os.platform() === "darwin") {
105-
await vscode.workspace
106-
.getConfiguration("rubyLsp")
107-
.update(
108-
"rubyVersionManager",
109-
{ identifier: ManagerIdentifier.Chruby },
110-
true,
111-
);
112-
113-
fs.mkdirSync(path.join(os.homedir(), ".rubies"), { recursive: true });
114-
fs.symlinkSync(
115-
`/Users/runner/hostedtoolcache/Ruby/${RUBY_VERSION}/arm64`,
116-
path.join(os.homedir(), ".rubies", RUBY_VERSION),
117-
);
118-
} else {
119-
await vscode.workspace
120-
.getConfiguration("rubyLsp")
121-
.update(
122-
"rubyVersionManager",
123-
{ identifier: ManagerIdentifier.RubyInstaller },
124-
true,
125-
);
126-
127-
fs.symlinkSync(
128-
path.join(
129-
"C:",
130-
"hostedtoolcache",
131-
"windows",
132-
"Ruby",
133-
RUBY_VERSION,
134-
"x64",
135-
),
136-
path.join("C:", `Ruby${MAJOR}${MINOR}-${os.arch()}`),
137-
);
138-
}
139-
}
89+
await ensureRubyInstallationPaths();
14090

14191
const ruby = new Ruby(
14292
context,

vscode/src/test/suite/rubyLsp.test.ts

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import path from "path";
2+
import assert from "assert";
3+
import fs from "fs";
4+
import os from "os";
5+
6+
import sinon from "sinon";
7+
import * as vscode from "vscode";
8+
import { beforeEach, afterEach, before, after } from "mocha";
9+
import { State } from "vscode-languageclient";
10+
11+
import { RubyLsp } from "../../rubyLsp";
12+
import { RUBY_VERSION } from "../rubyVersion";
13+
14+
import { FAKE_TELEMETRY } from "./fakeTelemetry";
15+
import { ensureRubyInstallationPaths } from "./testHelpers";
16+
17+
suite("Ruby LSP", () => {
18+
const context = {
19+
extensionMode: vscode.ExtensionMode.Test,
20+
subscriptions: [],
21+
workspaceState: {
22+
get: (_name: string) => undefined,
23+
update: (_name: string, _value: any) => Promise.resolve(),
24+
},
25+
extensionUri: vscode.Uri.file(
26+
path.dirname(path.dirname(path.dirname(__dirname))),
27+
),
28+
} as unknown as vscode.ExtensionContext;
29+
let workspacePath: string;
30+
let workspaceUri: vscode.Uri;
31+
let workspaceFolder: vscode.WorkspaceFolder;
32+
const originalSaveBeforeStart = vscode.workspace
33+
.getConfiguration("debug")
34+
.get("saveBeforeStart");
35+
36+
before(async () => {
37+
await vscode.workspace
38+
.getConfiguration("debug")
39+
.update("saveBeforeStart", "none", true);
40+
});
41+
42+
after(async () => {
43+
await vscode.workspace
44+
.getConfiguration("debug")
45+
.update("saveBeforeStart", originalSaveBeforeStart, true);
46+
});
47+
48+
beforeEach(() => {
49+
workspacePath = fs.mkdtempSync(
50+
path.join(os.tmpdir(), "ruby-lsp-integration-test-"),
51+
);
52+
workspaceUri = vscode.Uri.file(workspacePath);
53+
workspaceFolder = {
54+
uri: workspaceUri,
55+
name: path.basename(workspacePath),
56+
index: 0,
57+
};
58+
});
59+
60+
afterEach(() => {
61+
fs.rmSync(workspacePath, { recursive: true, force: true });
62+
});
63+
64+
test("launching debugger in a project with local Bundler settings and composed bundle", async () => {
65+
fs.writeFileSync(path.join(workspacePath, "test.rb"), "1 + 1");
66+
fs.writeFileSync(path.join(workspacePath, ".ruby-version"), RUBY_VERSION);
67+
fs.writeFileSync(
68+
path.join(workspacePath, "Gemfile"),
69+
'source "https://rubygems.org"\n',
70+
);
71+
fs.writeFileSync(
72+
path.join(workspacePath, "Gemfile.lock"),
73+
[
74+
"GEM",
75+
" remote: https://rubygems.org/",
76+
" specs:",
77+
"",
78+
"PLATFORMS",
79+
" arm64-darwin-23",
80+
" ruby",
81+
"",
82+
"DEPENDENCIES",
83+
"",
84+
"BUNDLED WITH",
85+
" 2.5.16",
86+
].join("\n"),
87+
);
88+
fs.mkdirSync(path.join(workspacePath, ".bundle"));
89+
fs.writeFileSync(
90+
path.join(workspacePath, ".bundle", "config"),
91+
`BUNDLE_PATH: ${path.join("vendor", "bundle")}`,
92+
);
93+
94+
await ensureRubyInstallationPaths();
95+
96+
const rubyLsp = new RubyLsp(context, FAKE_TELEMETRY);
97+
98+
try {
99+
await rubyLsp.activate(workspaceFolder);
100+
101+
const client = rubyLsp.workspaces.get(
102+
workspaceFolder.uri.toString(),
103+
)!.lspClient!;
104+
105+
if (client.state !== State.Running) {
106+
await new Promise<void>((resolve) => {
107+
const callback = client.onDidChangeState(() => {
108+
if (client.state === State.Running) {
109+
callback.dispose();
110+
resolve();
111+
}
112+
});
113+
});
114+
}
115+
} catch (error: any) {
116+
assert.fail(
117+
`Failed to activate Ruby LSP: ${error.message}\n\n${error.stack}`,
118+
);
119+
}
120+
121+
const stub = sinon.stub(vscode.window, "activeTextEditor").get(() => {
122+
return {
123+
document: {
124+
uri: vscode.Uri.file(path.join(workspacePath, "test.rb")),
125+
},
126+
} as vscode.TextEditor;
127+
});
128+
129+
const getWorkspaceStub = sinon
130+
.stub(vscode.workspace, "getWorkspaceFolder")
131+
.returns(workspaceFolder);
132+
133+
try {
134+
await vscode.debug.startDebugging(workspaceFolder, {
135+
type: "ruby_lsp",
136+
name: "Debug",
137+
request: "launch",
138+
program: `ruby ${path.join(workspacePath, "test.rb")}`,
139+
});
140+
} catch (error: any) {
141+
assert.fail(`Failed to launch debugger: ${error.message}`);
142+
}
143+
144+
// The debugger might take a bit of time to disconnect from the editor. We need to perform cleanup when we receive
145+
// the termination callback or else we try to dispose of the debugger client too early, but we need to wait for that
146+
// so that we can clean up stubs otherwise they leak into other tests.
147+
await new Promise<void>((resolve) => {
148+
vscode.debug.onDidTerminateDebugSession((_session) => {
149+
stub.restore();
150+
getWorkspaceStub.restore();
151+
152+
context.subscriptions.forEach((subscription) => {
153+
if (!("logLevel" in subscription)) {
154+
subscription.dispose();
155+
}
156+
});
157+
158+
resolve();
159+
});
160+
});
161+
}).timeout(90000);
162+
});

vscode/src/test/suite/testController.test.ts

+5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as assert from "assert";
22

33
import * as vscode from "vscode";
44
import { CodeLens } from "vscode-languageclient/node";
5+
import { afterEach } from "mocha";
56

67
import { TestController } from "../../testController";
78
import { Command } from "../../common";
@@ -18,6 +19,10 @@ suite("TestController", () => {
1819
},
1920
} as unknown as vscode.ExtensionContext;
2021

22+
afterEach(() => {
23+
context.subscriptions.forEach((subscription) => subscription.dispose());
24+
});
25+
2126
test("createTestItems doesn't break when there's a missing group", () => {
2227
const controller = new TestController(
2328
context,

vscode/src/test/suite/testHelpers.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* eslint-disable no-process-env */
2+
3+
import os from "os";
4+
import fs from "fs";
5+
import path from "path";
6+
7+
import * as vscode from "vscode";
8+
9+
import { ManagerIdentifier } from "../../ruby";
10+
import { RUBY_VERSION } from "../rubyVersion";
11+
12+
export async function ensureRubyInstallationPaths() {
13+
const [major, minor, _patch] = RUBY_VERSION.split(".");
14+
// Ensure that we're activating the correct Ruby version on CI
15+
if (process.env.CI) {
16+
if (os.platform() === "linux") {
17+
await vscode.workspace
18+
.getConfiguration("rubyLsp")
19+
.update(
20+
"rubyVersionManager",
21+
{ identifier: ManagerIdentifier.Chruby },
22+
true,
23+
);
24+
25+
fs.mkdirSync(path.join(os.homedir(), ".rubies"), { recursive: true });
26+
fs.symlinkSync(
27+
`/opt/hostedtoolcache/Ruby/${RUBY_VERSION}/x64`,
28+
path.join(os.homedir(), ".rubies", RUBY_VERSION),
29+
);
30+
} else if (os.platform() === "darwin") {
31+
await vscode.workspace
32+
.getConfiguration("rubyLsp")
33+
.update(
34+
"rubyVersionManager",
35+
{ identifier: ManagerIdentifier.Chruby },
36+
true,
37+
);
38+
39+
fs.mkdirSync(path.join(os.homedir(), ".rubies"), { recursive: true });
40+
fs.symlinkSync(
41+
`/Users/runner/hostedtoolcache/Ruby/${RUBY_VERSION}/arm64`,
42+
path.join(os.homedir(), ".rubies", RUBY_VERSION),
43+
);
44+
} else {
45+
await vscode.workspace
46+
.getConfiguration("rubyLsp")
47+
.update(
48+
"rubyVersionManager",
49+
{ identifier: ManagerIdentifier.RubyInstaller },
50+
true,
51+
);
52+
53+
fs.symlinkSync(
54+
path.join(
55+
"C:",
56+
"hostedtoolcache",
57+
"windows",
58+
"Ruby",
59+
RUBY_VERSION,
60+
"x64",
61+
),
62+
path.join("C:", `Ruby${major}${minor}-${os.arch()}`),
63+
);
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)