Skip to content

Commit 2760701

Browse files
authored
Automatically clear Ruby workspace cache if the path no longer exists (#3573)
1 parent 90437cc commit 2760701

File tree

3 files changed

+127
-65
lines changed

3 files changed

+127
-65
lines changed

vscode/src/ruby.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,7 @@ export class Ruby implements RubyInterface {
125125
this.versionManager = versionManager;
126126
this._error = false;
127127

128-
const workspaceRubyPath = this.context.workspaceState.get<
129-
string | undefined
130-
>(`rubyLsp.workspaceRubyPath.${this.workspaceFolder.name}`);
128+
const workspaceRubyPath = await this.cachedWorkspaceRubyPath();
131129

132130
if (workspaceRubyPath) {
133131
// If a workspace specific Ruby path is configured, then we use that to activate the environment
@@ -504,4 +502,26 @@ export class Ruby implements RubyInterface {
504502

505503
return this.manuallySelectRuby();
506504
}
505+
506+
private async cachedWorkspaceRubyPath() {
507+
const workspaceRubyPath = this.context.workspaceState.get<
508+
string | undefined
509+
>(`rubyLsp.workspaceRubyPath.${this.workspaceFolder.name}`);
510+
511+
if (!workspaceRubyPath) {
512+
return undefined;
513+
}
514+
515+
try {
516+
await vscode.workspace.fs.stat(vscode.Uri.file(workspaceRubyPath));
517+
return workspaceRubyPath;
518+
} catch (error: any) {
519+
// If the user selected a Ruby path and then uninstalled it, we need to clear the the cached path
520+
this.context.workspaceState.update(
521+
`rubyLsp.workspaceRubyPath.${this.workspaceFolder.name}`,
522+
undefined,
523+
);
524+
return undefined;
525+
}
526+
}
507527
}

vscode/src/test/suite/helpers.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,23 @@ export function createRubySymlinks() {
4444
}
4545
}
4646

47+
class FakeWorkspaceState implements vscode.Memento {
48+
private store: Record<string, any> = {};
49+
50+
keys(): ReadonlyArray<string> {
51+
return Object.keys(this.store);
52+
}
53+
54+
get<T>(key: string): T | undefined {
55+
return this.store[key];
56+
}
57+
58+
update(key: string, value: any): Thenable<void> {
59+
this.store[key] = value;
60+
return Promise.resolve();
61+
}
62+
}
63+
4764
export const LSP_WORKSPACE_PATH = path.dirname(
4865
path.dirname(path.dirname(path.dirname(__dirname))),
4966
);
@@ -56,9 +73,6 @@ export const LSP_WORKSPACE_FOLDER: vscode.WorkspaceFolder = {
5673
export const CONTEXT = {
5774
extensionMode: vscode.ExtensionMode.Test,
5875
subscriptions: [],
59-
workspaceState: {
60-
get: (_name: string) => undefined,
61-
update: (_name: string, _value: any) => Promise.resolve(),
62-
},
76+
workspaceState: new FakeWorkspaceState(),
6377
extensionUri: vscode.Uri.joinPath(LSP_WORKSPACE_URI, "vscode"),
6478
} as unknown as vscode.ExtensionContext;

vscode/src/test/suite/ruby.test.ts

Lines changed: 86 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path from "path";
44

55
import * as vscode from "vscode";
66
import sinon from "sinon";
7+
import { afterEach, beforeEach } from "mocha";
78

89
import { Ruby, ManagerIdentifier } from "../../ruby";
910
import { WorkspaceChannel } from "../../workspaceChannel";
@@ -16,6 +17,7 @@ import {
1617
VALUE_SEPARATOR,
1718
} from "../../ruby/versionManager";
1819

20+
import { CONTEXT } from "./helpers";
1921
import { FAKE_TELEMETRY } from "./fakeTelemetry";
2022

2123
suite("Ruby environment activation", () => {
@@ -27,37 +29,36 @@ suite("Ruby environment activation", () => {
2729
name: path.basename(workspacePath),
2830
index: 0,
2931
};
30-
const context = {
31-
extensionMode: vscode.ExtensionMode.Test,
32-
workspaceState: {
33-
get: () => undefined,
34-
update: () => undefined,
35-
},
36-
extensionUri: vscode.Uri.file(path.join(workspacePath, "vscode")),
37-
} as unknown as vscode.ExtensionContext;
3832
const outputChannel = new WorkspaceChannel("fake", LOG_CHANNEL);
33+
let sandbox: sinon.SinonSandbox;
34+
35+
beforeEach(() => {
36+
sandbox = sinon.createSandbox();
37+
});
38+
39+
afterEach(() => {
40+
sandbox.restore();
41+
});
3942

4043
test("Activate fetches Ruby information when outside of Ruby LSP", async () => {
4144
const manager = process.env.CI
4245
? ManagerIdentifier.None
4346
: ManagerIdentifier.Chruby;
4447

45-
const configStub = sinon
46-
.stub(vscode.workspace, "getConfiguration")
47-
.returns({
48-
get: (name: string) => {
49-
if (name === "rubyVersionManager") {
50-
return { identifier: manager };
51-
} else if (name === "bundleGemfile") {
52-
return "";
53-
}
48+
sandbox.stub(vscode.workspace, "getConfiguration").returns({
49+
get: (name: string) => {
50+
if (name === "rubyVersionManager") {
51+
return { identifier: manager };
52+
} else if (name === "bundleGemfile") {
53+
return "";
54+
}
5455

55-
return undefined;
56-
},
57-
} as unknown as vscode.WorkspaceConfiguration);
56+
return undefined;
57+
},
58+
} as unknown as vscode.WorkspaceConfiguration);
5859

5960
const ruby = new Ruby(
60-
context,
61+
CONTEXT,
6162
workspaceFolder,
6263
outputChannel,
6364
FAKE_TELEMETRY,
@@ -70,31 +71,27 @@ suite("Ruby environment activation", () => {
7071
undefined,
7172
"Expected YJIT support to be set to true or false",
7273
);
73-
74-
configStub.restore();
7574
}).timeout(10000);
7675

7776
test("Deletes verbose and GC settings from activated environment", async () => {
7877
const manager = process.env.CI
7978
? ManagerIdentifier.None
8079
: ManagerIdentifier.Chruby;
8180

82-
const configStub = sinon
83-
.stub(vscode.workspace, "getConfiguration")
84-
.returns({
85-
get: (name: string) => {
86-
if (name === "rubyVersionManager") {
87-
return { identifier: manager };
88-
} else if (name === "bundleGemfile") {
89-
return "";
90-
}
81+
sandbox.stub(vscode.workspace, "getConfiguration").returns({
82+
get: (name: string) => {
83+
if (name === "rubyVersionManager") {
84+
return { identifier: manager };
85+
} else if (name === "bundleGemfile") {
86+
return "";
87+
}
9188

92-
return undefined;
93-
},
94-
} as unknown as vscode.WorkspaceConfiguration);
89+
return undefined;
90+
},
91+
} as unknown as vscode.WorkspaceConfiguration);
9592

9693
const ruby = new Ruby(
97-
context,
94+
CONTEXT,
9895
workspaceFolder,
9996
outputChannel,
10097
FAKE_TELEMETRY,
@@ -111,23 +108,20 @@ suite("Ruby environment activation", () => {
111108
delete process.env.VERBOSE;
112109
delete process.env.DEBUG;
113110
delete process.env.RUBY_GC_HEAP_GROWTH_FACTOR;
114-
configStub.restore();
115111
});
116112

117113
test("Sets gem path for version managers based on shims", async () => {
118-
const configStub = sinon
119-
.stub(vscode.workspace, "getConfiguration")
120-
.returns({
121-
get: (name: string) => {
122-
if (name === "rubyVersionManager") {
123-
return { identifier: ManagerIdentifier.Rbenv };
124-
} else if (name === "bundleGemfile") {
125-
return "";
126-
}
127-
128-
return undefined;
129-
},
130-
} as unknown as vscode.WorkspaceConfiguration);
114+
sandbox.stub(vscode.workspace, "getConfiguration").returns({
115+
get: (name: string) => {
116+
if (name === "rubyVersionManager") {
117+
return { identifier: ManagerIdentifier.Rbenv };
118+
} else if (name === "bundleGemfile") {
119+
return "";
120+
}
121+
122+
return undefined;
123+
},
124+
} as unknown as vscode.WorkspaceConfiguration);
131125

132126
const envStub = [
133127
"3.3.5",
@@ -136,20 +130,18 @@ suite("Ruby environment activation", () => {
136130
`ANY${VALUE_SEPARATOR}true`,
137131
].join(FIELD_SEPARATOR);
138132

139-
const execStub = sinon.stub(common, "asyncExec").resolves({
133+
sandbox.stub(common, "asyncExec").resolves({
140134
stdout: "",
141135
stderr: `${ACTIVATION_SEPARATOR}${envStub}${ACTIVATION_SEPARATOR}`,
142136
});
143137

144138
const ruby = new Ruby(
145-
context,
139+
CONTEXT,
146140
workspaceFolder,
147141
outputChannel,
148142
FAKE_TELEMETRY,
149143
);
150144
await ruby.activateRuby();
151-
execStub.restore();
152-
configStub.restore();
153145

154146
assert.deepStrictEqual(ruby.gemPath, [
155147
"~/.gem/ruby/3.3.5",
@@ -159,7 +151,7 @@ suite("Ruby environment activation", () => {
159151

160152
test("mergeComposedEnv merges environment variables", () => {
161153
const ruby = new Ruby(
162-
context,
154+
CONTEXT,
163155
workspaceFolder,
164156
outputChannel,
165157
FAKE_TELEMETRY,
@@ -176,9 +168,9 @@ suite("Ruby environment activation", () => {
176168

177169
test("Ignores untrusted workspace for telemetry", async () => {
178170
const telemetry = { ...FAKE_TELEMETRY, logError: sinon.stub() };
179-
const ruby = new Ruby(context, workspaceFolder, outputChannel, telemetry);
171+
const ruby = new Ruby(CONTEXT, workspaceFolder, outputChannel, telemetry);
180172

181-
const failureStub = sinon
173+
sandbox
182174
.stub(Shadowenv.prototype, "activate")
183175
.rejects(new UntrustedWorkspaceError());
184176

@@ -187,7 +179,43 @@ suite("Ruby environment activation", () => {
187179
});
188180

189181
assert.ok(!telemetry.logError.called);
182+
});
190183

191-
failureStub.restore();
184+
test("Clears outdated workspace Ruby path caches", async () => {
185+
const manager = process.env.CI
186+
? ManagerIdentifier.None
187+
: ManagerIdentifier.Chruby;
188+
189+
sandbox.stub(vscode.workspace, "getConfiguration").returns({
190+
get: (name: string) => {
191+
if (name === "rubyVersionManager") {
192+
return { identifier: manager };
193+
} else if (name === "bundleGemfile") {
194+
return "";
195+
}
196+
197+
return undefined;
198+
},
199+
} as unknown as vscode.WorkspaceConfiguration);
200+
201+
await CONTEXT.workspaceState.update(
202+
`rubyLsp.workspaceRubyPath.${workspaceFolder.name}`,
203+
"/totally/non/existent/path/ruby",
204+
);
205+
const ruby = new Ruby(
206+
CONTEXT,
207+
workspaceFolder,
208+
outputChannel,
209+
FAKE_TELEMETRY,
210+
);
211+
212+
await ruby.activateRuby();
213+
214+
assert.strictEqual(
215+
CONTEXT.workspaceState.get(
216+
`rubyLsp.workspaceRubyPath.${workspaceFolder.name}`,
217+
),
218+
undefined,
219+
);
192220
});
193221
});

0 commit comments

Comments
 (0)