From 53c4ba1612b82daa6592f471e307731664e87c75 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Tue, 28 Apr 2026 13:14:23 -0400 Subject: [PATCH] Decouple Ruby tests from specific machine setup --- vscode/src/test/suite/helpers.ts | 38 +++++++ vscode/src/test/suite/ruby.test.ts | 162 ++++++++++++++--------------- 2 files changed, 114 insertions(+), 86 deletions(-) diff --git a/vscode/src/test/suite/helpers.ts b/vscode/src/test/suite/helpers.ts index 6c48a8bfb3..248be08716 100644 --- a/vscode/src/test/suite/helpers.ts +++ b/vscode/src/test/suite/helpers.ts @@ -3,6 +3,7 @@ import os from "os"; import fs from "fs"; import * as vscode from "vscode"; +import sinon from "sinon"; import { MAJOR, MINOR, RUBY_VERSION } from "../rubyVersion"; @@ -57,6 +58,43 @@ export const LSP_WORKSPACE_FOLDER: vscode.WorkspaceFolder = { export type FakeContext = vscode.ExtensionContext & { dispose: () => void }; +// Stubs `vscode.workspace.getConfiguration` so that requested keys return stubbed values, while any unspecified keys +// (or unspecified sections) fall through to the real configuration +export function stubWorkspaceConfiguration( + sandbox: sinon.SinonSandbox, + stubs: Record>, +): sinon.SinonStub { + const original = vscode.workspace.getConfiguration.bind(vscode.workspace); + + return sandbox + .stub(vscode.workspace, "getConfiguration") + .callsFake((section?: string, scope?: vscode.ConfigurationScope | null) => { + const real = original(section, scope); + const sectionStubs = stubs[section ?? ""]; + + if (!sectionStubs) { + return real; + } + + // Can't Proxy a WorkspaceConfiguration: VS Code defines `get` as non-configurable + non-writable, which + // violates Proxy invariants. Instead, build a delegating object that overrides `get` and forwards everything + // else to the real configuration (preserving `this` via bind so private state access still works). + const wrapper: vscode.WorkspaceConfiguration = { + get(key: string, defaultValue?: unknown) { + if (key in sectionStubs) { + return sectionStubs[key]; + } + return defaultValue === undefined ? real.get(key) : real.get(key, defaultValue); + }, + has: real.has.bind(real), + inspect: real.inspect.bind(real), + update: real.update.bind(real), + }; + + return wrapper; + }); +} + export function createContext() { const subscriptions: vscode.Disposable[] = []; diff --git a/vscode/src/test/suite/ruby.test.ts b/vscode/src/test/suite/ruby.test.ts index e0a093c179..3750c5e60b 100644 --- a/vscode/src/test/suite/ruby.test.ts +++ b/vscode/src/test/suite/ruby.test.ts @@ -15,7 +15,7 @@ import { Shadowenv, UntrustedWorkspaceError } from "../../ruby/shadowenv"; import { Chruby } from "../../ruby/chruby"; import { ACTIVATION_SEPARATOR, FIELD_SEPARATOR, MissingRubyError, VALUE_SEPARATOR } from "../../ruby/versionManager"; -import { createContext, FakeContext } from "./helpers"; +import { createContext, FakeContext, stubWorkspaceConfiguration } from "./helpers"; import { FAKE_TELEMETRY } from "./fakeTelemetry"; suite("Ruby environment activation", () => { @@ -39,70 +39,70 @@ suite("Ruby environment activation", () => { context.dispose(); }); - test("Activate fetches Ruby information when outside of Ruby LSP", async () => { - const manager = process.env.CI ? ManagerIdentifier.None : ManagerIdentifier.Chruby; + test("Populates Ruby version and YJIT support from the activation script", async () => { + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.None }, + bundleGemfile: "", + }, + }); - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "rubyVersionManager") { - return { identifier: manager }; - } else if (name === "bundleGemfile") { - return ""; - } + const envStub = [ + "3.3.5", + "~/.gem/ruby/3.3.5,/opt/rubies/3.3.5/lib/ruby/gems/3.3.0", + "true", + `ANY${VALUE_SEPARATOR}true`, + ].join(FIELD_SEPARATOR); - return undefined; - }, - } as unknown as vscode.WorkspaceConfiguration); + sandbox.stub(common, "asyncExec").resolves({ + stdout: "", + stderr: `${ACTIVATION_SEPARATOR}${envStub}${ACTIVATION_SEPARATOR}`, + }); const ruby = new Ruby(context, workspaceFolder, outputChannel, FAKE_TELEMETRY); await ruby.activateRuby(); - assert.ok(ruby.rubyVersion, "Expected Ruby version to be set"); - assert.notStrictEqual(ruby.yjitEnabled, undefined, "Expected YJIT support to be set to true or false"); - }).timeout(10000); + assert.strictEqual(ruby.rubyVersion, "3.3.5"); + assert.strictEqual(ruby.yjitEnabled, true); + }); test("Deletes verbose and GC settings from activated environment", async () => { - const manager = process.env.CI ? ManagerIdentifier.None : ManagerIdentifier.Chruby; + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.None }, + bundleGemfile: "", + }, + }); - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "rubyVersionManager") { - return { identifier: manager }; - } else if (name === "bundleGemfile") { - return ""; - } + const envStub = [ + "3.3.5", + "~/.gem/ruby/3.3.5,/opt/rubies/3.3.5/lib/ruby/gems/3.3.0", + "true", + `VERBOSE${VALUE_SEPARATOR}1`, + `DEBUG${VALUE_SEPARATOR}WARN`, + `RUBY_GC_HEAP_GROWTH_FACTOR${VALUE_SEPARATOR}1.7`, + ].join(FIELD_SEPARATOR); - return undefined; - }, - } as unknown as vscode.WorkspaceConfiguration); + sandbox.stub(common, "asyncExec").resolves({ + stdout: "", + stderr: `${ACTIVATION_SEPARATOR}${envStub}${ACTIVATION_SEPARATOR}`, + }); const ruby = new Ruby(context, workspaceFolder, outputChannel, FAKE_TELEMETRY); - - process.env.VERBOSE = "1"; - process.env.DEBUG = "WARN"; - process.env.RUBY_GC_HEAP_GROWTH_FACTOR = "1.7"; await ruby.activateRuby(); assert.strictEqual(ruby.env.VERBOSE, undefined); assert.strictEqual(ruby.env.DEBUG, undefined); assert.strictEqual(ruby.env.RUBY_GC_HEAP_GROWTH_FACTOR, undefined); - delete process.env.VERBOSE; - delete process.env.DEBUG; - delete process.env.RUBY_GC_HEAP_GROWTH_FACTOR; }); test("Sets gem path for version managers based on shims", async () => { - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "rubyVersionManager") { - return { identifier: ManagerIdentifier.Rbenv }; - } else if (name === "bundleGemfile") { - return ""; - } - - return undefined; + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.Rbenv }, + bundleGemfile: "", }, - } as unknown as vscode.WorkspaceConfiguration); + }); const envStub = [ "3.3.5", @@ -163,19 +163,24 @@ suite("Ruby environment activation", () => { }); test("Clears outdated workspace Ruby path caches", async () => { - const manager = process.env.CI ? ManagerIdentifier.None : ManagerIdentifier.Chruby; + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.None }, + bundleGemfile: "", + }, + }); - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "rubyVersionManager") { - return { identifier: manager }; - } else if (name === "bundleGemfile") { - return ""; - } + const envStub = [ + "3.3.5", + "~/.gem/ruby/3.3.5,/opt/rubies/3.3.5/lib/ruby/gems/3.3.0", + "true", + `ANY${VALUE_SEPARATOR}true`, + ].join(FIELD_SEPARATOR); - return undefined; - }, - } as unknown as vscode.WorkspaceConfiguration); + sandbox.stub(common, "asyncExec").resolves({ + stdout: "", + stderr: `${ACTIVATION_SEPARATOR}${envStub}${ACTIVATION_SEPARATOR}`, + }); await context.workspaceState.update( `rubyLsp.workspaceRubyPath.${workspaceFolder.name}`, @@ -202,18 +207,13 @@ suite("Ruby environment activation", () => { index: 0, }; - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "rubyVersionManager") { - return { identifier: ManagerIdentifier.None }; - } else if (name === "bundleGemfile") { - // eslint-disable-next-line no-template-curly-in-string - return "${workspaceFolder}/Gemfile"; - } - - return undefined; + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.None }, + // eslint-disable-next-line no-template-curly-in-string + bundleGemfile: "${workspaceFolder}/Gemfile", }, - } as unknown as vscode.WorkspaceConfiguration); + }); const envStub = [ "3.3.5", @@ -235,17 +235,12 @@ suite("Ruby environment activation", () => { }); test("Appends YJIT flag to existing RUBYOPT for Ruby 3.2", async () => { - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "rubyVersionManager") { - return { identifier: ManagerIdentifier.None }; - } else if (name === "bundleGemfile") { - return ""; - } - - return undefined; + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.None }, + bundleGemfile: "", }, - } as unknown as vscode.WorkspaceConfiguration); + }); const envStub = [ "3.2.0", @@ -275,17 +270,12 @@ suite("Ruby environment activation", () => { const nonExistentGemfile = path.join(tmpPath, "nonexistent", "Gemfile"); - sandbox.stub(vscode.workspace, "getConfiguration").returns({ - get: (name: string) => { - if (name === "bundleGemfile") { - return nonExistentGemfile; - } else if (name === "rubyVersionManager") { - return { identifier: ManagerIdentifier.None }; - } - - return undefined; + stubWorkspaceConfiguration(sandbox, { + rubyLsp: { + rubyVersionManager: { identifier: ManagerIdentifier.None }, + bundleGemfile: nonExistentGemfile, }, - } as unknown as vscode.WorkspaceConfiguration); + }); const ruby = new Ruby(context, tmpWorkspaceFolder, outputChannel, FAKE_TELEMETRY);