Skip to content

Commit b4bd439

Browse files
committed
Add debugger test with project Bundler settings
1 parent 02f86f3 commit b4bd439

File tree

5 files changed

+210
-12
lines changed

5 files changed

+210
-12
lines changed

vscode/src/debugger.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,12 +102,24 @@ export class Debugger
102102
debugConfiguration: vscode.DebugConfiguration,
103103
_token?: vscode.CancellationToken,
104104
): vscode.ProviderResult<vscode.DebugConfiguration> {
105-
const workspace = this.workspaceResolver(folder?.uri);
105+
// On certain occasions, the objects passed to this method are serialized. In particular for the URI, we have to
106+
// ensure we're dealing with a `vscode.Uri` object and not a plain object
107+
const uriAttributes =
108+
folder?.uri ?? debugConfiguration.workspaceFolder?.uri;
109+
110+
if (!uriAttributes) {
111+
throw new Error(`Couldn't find a workspace to start debugging`);
112+
}
113+
114+
const uri =
115+
uriAttributes instanceof vscode.Uri
116+
? uriAttributes
117+
: vscode.Uri.from(uriAttributes);
118+
119+
const workspace = this.workspaceResolver(uri);
106120

107121
if (!workspace) {
108-
throw new Error(
109-
`Couldn't find a workspace for URI: ${folder?.uri} or editor: ${vscode.window.activeTextEditor}`,
110-
);
122+
throw new Error(`Couldn't find a workspace for URI: ${uri}`);
111123
}
112124

113125
if (debugConfiguration.env) {
@@ -120,10 +132,8 @@ export class Debugger
120132
debugConfiguration.env = workspace.ruby.env;
121133
}
122134

123-
const workspaceUri = workspace.workspaceFolder.uri;
124-
125135
debugConfiguration.targetFolder = {
126-
path: workspaceUri.fsPath,
136+
path: uri.fsPath,
127137
name: workspace.workspaceFolder.name,
128138
};
129139

@@ -133,7 +143,7 @@ export class Debugger
133143
return debugConfiguration;
134144
}
135145

136-
const customBundleUri = vscode.Uri.joinPath(workspaceUri, ".ruby-lsp");
146+
const customBundleUri = vscode.Uri.joinPath(uri, ".ruby-lsp");
137147

138148
return vscode.workspace.fs.readDirectory(customBundleUri).then(
139149
(value) => {

vscode/src/rubyLsp.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ export class RubyLsp {
176176
}
177177
}
178178

179+
getWorkspace(uri: vscode.Uri): Workspace | undefined {
180+
return this.workspaces.get(uri.toString());
181+
}
182+
179183
private async activateWorkspace(
180184
workspaceFolder: vscode.WorkspaceFolder,
181185
eager: boolean,
@@ -693,10 +697,6 @@ export class RubyLsp {
693697
return this.getWorkspace(workspaceFolder.uri);
694698
}
695699

696-
private getWorkspace(uri: vscode.Uri): Workspace | undefined {
697-
return this.workspaces.get(uri.toString());
698-
}
699-
700700
private workspaceResolver(
701701
uri: vscode.Uri | undefined,
702702
): Workspace | undefined {

vscode/src/test/suite/debugger.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ suite("Debugger", () => {
247247
name: "Debug",
248248
request: "launch",
249249
program: `ruby ${path.join(tmpPath, "test.rb")}`,
250+
workspaceFolder,
250251
});
251252
} catch (error: any) {
252253
assert.fail(`Failed to launch debugger: ${error.message}`);
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/* eslint-disable no-process-env */
2+
import path from "path";
3+
import assert from "assert";
4+
import fs from "fs";
5+
import os from "os";
6+
7+
import sinon from "sinon";
8+
import * as vscode from "vscode";
9+
import { beforeEach, afterEach } from "mocha";
10+
import { State } from "vscode-languageclient";
11+
12+
import { RubyLsp } from "../../rubyLsp";
13+
import { RUBY_VERSION } from "../rubyVersion";
14+
import { ManagerIdentifier } from "../../ruby";
15+
16+
import { FAKE_TELEMETRY } from "./fakeTelemetry";
17+
import { createRubySymlinks } from "./helpers";
18+
19+
suite("Ruby LSP", () => {
20+
const context = {
21+
extensionMode: vscode.ExtensionMode.Test,
22+
subscriptions: [],
23+
workspaceState: {
24+
get: (_name: string) => undefined,
25+
update: (_name: string, _value: any) => Promise.resolve(),
26+
},
27+
extensionUri: vscode.Uri.file(
28+
path.dirname(path.dirname(path.dirname(__dirname))),
29+
),
30+
} as unknown as vscode.ExtensionContext;
31+
let workspacePath: string;
32+
let workspaceUri: vscode.Uri;
33+
let workspaceFolder: vscode.WorkspaceFolder;
34+
const originalSaveBeforeStart = vscode.workspace
35+
.getConfiguration("debug")
36+
.get("saveBeforeStart");
37+
let workspacesStub: sinon.SinonStub;
38+
39+
beforeEach(async () => {
40+
await vscode.workspace
41+
.getConfiguration("debug")
42+
.update("saveBeforeStart", "none", true);
43+
workspacePath = fs.mkdtempSync(
44+
path.join(os.tmpdir(), "ruby-lsp-integration-test-"),
45+
);
46+
workspaceUri = vscode.Uri.file(workspacePath);
47+
workspaceFolder = {
48+
uri: workspaceUri,
49+
name: path.basename(workspacePath),
50+
index: 0,
51+
};
52+
53+
workspacesStub = sinon
54+
.stub(vscode.workspace, "workspaceFolders")
55+
.get(() => [workspaceFolder]);
56+
57+
if (process.env.CI) {
58+
createRubySymlinks();
59+
}
60+
});
61+
62+
afterEach(async () => {
63+
workspacesStub.restore();
64+
fs.rmSync(workspacePath, { recursive: true, force: true });
65+
66+
await vscode.workspace
67+
.getConfiguration("debug")
68+
.update("saveBeforeStart", originalSaveBeforeStart, true);
69+
});
70+
71+
function writeFileSetup() {
72+
fs.writeFileSync(path.join(workspacePath, "test.rb"), "1 + 1");
73+
fs.writeFileSync(path.join(workspacePath, ".ruby-version"), RUBY_VERSION);
74+
fs.writeFileSync(
75+
path.join(workspacePath, "Gemfile"),
76+
'source "https://rubygems.org"\n',
77+
);
78+
fs.writeFileSync(
79+
path.join(workspacePath, "Gemfile.lock"),
80+
[
81+
"GEM",
82+
" remote: https://rubygems.org/",
83+
" specs:",
84+
"",
85+
"PLATFORMS",
86+
" arm64-darwin-23",
87+
" ruby",
88+
"",
89+
"DEPENDENCIES",
90+
"",
91+
"BUNDLED WITH",
92+
" 2.5.16",
93+
].join("\n"),
94+
);
95+
fs.mkdirSync(path.join(workspacePath, ".bundle"));
96+
fs.writeFileSync(
97+
path.join(workspacePath, ".bundle", "config"),
98+
`BUNDLE_PATH: ${path.join("vendor", "bundle")}`,
99+
);
100+
}
101+
102+
test("launching debugger in a project with local Bundler settings and composed bundle", async () => {
103+
writeFileSetup();
104+
105+
if (process.env.CI && os.platform() === "win32") {
106+
await vscode.workspace
107+
.getConfiguration("rubyLsp")
108+
.update(
109+
"rubyVersionManager",
110+
{ identifier: ManagerIdentifier.RubyInstaller },
111+
true,
112+
);
113+
} else if (process.env.CI) {
114+
await vscode.workspace
115+
.getConfiguration("rubyLsp")
116+
.update(
117+
"rubyVersionManager",
118+
{ identifier: ManagerIdentifier.Chruby },
119+
true,
120+
);
121+
}
122+
123+
const rubyLsp = new RubyLsp(context, FAKE_TELEMETRY);
124+
125+
try {
126+
await rubyLsp.activate();
127+
} catch (error: any) {
128+
assert.fail(
129+
`Failed to activate Ruby LSP: ${error.message}\n\n${error.stack}`,
130+
);
131+
}
132+
133+
const client = rubyLsp.getWorkspace(workspaceFolder.uri)!.lspClient!;
134+
135+
if (client.state === State.Starting) {
136+
await new Promise<void>((resolve) => {
137+
const callback = client.onDidChangeState((event) => {
138+
if (event.newState === State.Running) {
139+
callback.dispose();
140+
resolve();
141+
}
142+
});
143+
});
144+
}
145+
146+
assert.strictEqual(client.state, State.Running);
147+
assert.ok(
148+
fs.existsSync(path.join(workspacePath, ".ruby-lsp", "Gemfile.lock")),
149+
);
150+
assert.match(
151+
fs
152+
.readFileSync(path.join(workspacePath, ".ruby-lsp", "Gemfile.lock"))
153+
.toString(),
154+
/debug/,
155+
);
156+
157+
try {
158+
await vscode.debug.startDebugging(workspaceFolder, {
159+
type: "ruby_lsp",
160+
name: "Debug",
161+
request: "launch",
162+
program: `ruby ${path.join(workspacePath, "test.rb")}`,
163+
workspaceFolder,
164+
});
165+
} catch (error: any) {
166+
assert.fail(`Failed to launch debugger: ${error.message}`);
167+
}
168+
169+
await new Promise<void>((resolve) => {
170+
const callback = vscode.debug.onDidTerminateDebugSession((_session) => {
171+
context.subscriptions.forEach((subscription) => {
172+
if (!("logLevel" in subscription)) {
173+
subscription.dispose();
174+
}
175+
});
176+
177+
callback.dispose();
178+
resolve();
179+
});
180+
});
181+
}).timeout(90000);
182+
});

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

Lines changed: 5 additions & 0 deletions
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,

0 commit comments

Comments
 (0)