Skip to content

Commit d16cd97

Browse files
committed
Add debugger test with project Bundler settings
1 parent b003c17 commit d16cd97

File tree

6 files changed

+181
-13
lines changed

6 files changed

+181
-13
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: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
11+
import { RubyLsp } from "../../rubyLsp";
12+
import { RUBY_VERSION } from "../rubyVersion";
13+
import { ManagerIdentifier } from "../../ruby";
14+
15+
import { FAKE_TELEMETRY } from "./fakeTelemetry";
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+
let workspacesStub: sinon.SinonStub;
36+
37+
beforeEach(async () => {
38+
await vscode.workspace
39+
.getConfiguration("debug")
40+
.update("saveBeforeStart", "none", true);
41+
workspacePath = fs.mkdtempSync(
42+
path.join(os.tmpdir(), "ruby-lsp-integration-test-"),
43+
);
44+
workspaceUri = vscode.Uri.file(workspacePath);
45+
workspaceFolder = {
46+
uri: workspaceUri,
47+
name: path.basename(workspacePath),
48+
index: 0,
49+
};
50+
51+
workspacesStub = sinon
52+
.stub(vscode.workspace, "workspaceFolders")
53+
.get(() => [workspaceFolder]);
54+
});
55+
56+
afterEach(async () => {
57+
workspacesStub.restore();
58+
fs.rmSync(workspacePath, { recursive: true, force: true });
59+
60+
await vscode.workspace
61+
.getConfiguration("debug")
62+
.update("saveBeforeStart", originalSaveBeforeStart, true);
63+
});
64+
65+
function writeFileSetup() {
66+
fs.writeFileSync(path.join(workspacePath, "test.rb"), "1 + 1");
67+
fs.writeFileSync(path.join(workspacePath, ".ruby-version"), RUBY_VERSION);
68+
fs.writeFileSync(
69+
path.join(workspacePath, "Gemfile"),
70+
'source "https://rubygems.org"\n',
71+
);
72+
fs.writeFileSync(
73+
path.join(workspacePath, "Gemfile.lock"),
74+
[
75+
"GEM",
76+
" remote: https://rubygems.org/",
77+
" specs:",
78+
"",
79+
"PLATFORMS",
80+
" arm64-darwin-23",
81+
" ruby",
82+
"",
83+
"DEPENDENCIES",
84+
"",
85+
"BUNDLED WITH",
86+
" 2.5.16",
87+
].join("\n"),
88+
);
89+
fs.mkdirSync(path.join(workspacePath, ".bundle"));
90+
fs.writeFileSync(
91+
path.join(workspacePath, ".bundle", "config"),
92+
`BUNDLE_PATH: ${path.join("vendor", "bundle")}`,
93+
);
94+
}
95+
96+
test("launching debugger in a project with local Bundler settings and composed bundle", async () => {
97+
writeFileSetup();
98+
99+
if (process.env.CI) {
100+
await vscode.workspace
101+
.getConfiguration("rubyLsp")
102+
.update(
103+
"rubyVersionManager",
104+
{ identifier: ManagerIdentifier.None },
105+
true,
106+
);
107+
}
108+
109+
const rubyLsp = new RubyLsp(context, FAKE_TELEMETRY);
110+
111+
try {
112+
await rubyLsp.activate();
113+
} catch (error: any) {
114+
assert.fail(
115+
`Failed to activate Ruby LSP: ${error.message}\n\n${error.stack}`,
116+
);
117+
}
118+
119+
// Verify that the composed environment was properly merged into the Ruby object
120+
const workspace = rubyLsp.getWorkspace(workspaceFolder.uri)!;
121+
assert.match(workspace.ruby.env.BUNDLE_PATH!, /vendor(\/|\\)bundle/);
122+
assert.match(
123+
workspace.ruby.env.BUNDLE_GEMFILE!,
124+
/\.ruby-lsp(\/|\\)Gemfile/,
125+
);
126+
127+
try {
128+
await vscode.debug.startDebugging(workspaceFolder, {
129+
type: "ruby_lsp",
130+
name: "Debug",
131+
request: "launch",
132+
program: `ruby ${path.join(workspacePath, "test.rb")}`,
133+
workspaceFolder,
134+
});
135+
} catch (error: any) {
136+
assert.fail(`Failed to launch debugger: ${error.message}`);
137+
}
138+
139+
await new Promise<void>((resolve) => {
140+
const callback = vscode.debug.onDidTerminateDebugSession((_session) => {
141+
context.subscriptions.forEach((subscription) => {
142+
if (!("logLevel" in subscription)) {
143+
subscription.dispose();
144+
}
145+
});
146+
147+
callback.dispose();
148+
resolve();
149+
});
150+
});
151+
}).timeout(90000);
152+
});

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)