Skip to content

Commit 5874fe7

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

File tree

6 files changed

+233
-61
lines changed

6 files changed

+233
-61
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

+4-4
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,10 @@ export class RubyLsp {
119119

120120
// Activate the extension. This method should perform all actions necessary to start the extension, such as booting
121121
// 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];
122+
async activate(firstWorkspace = vscode.workspace.workspaceFolders?.[0]) {
123+
if (this.context.extensionMode !== vscode.ExtensionMode.Test) {
124+
await vscode.commands.executeCommand("testing.clearTestResults");
125+
}
126126

127127
// We only activate the first workspace eagerly to avoid running into performance and memory issues. Having too many
128128
// 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

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

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)