From fb2acec6769b31d92fb990d80b90e40ced9155f8 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Fri, 24 Jan 2025 16:51:50 -0500 Subject: [PATCH] Add debugger test with project Bundler settings --- test/fixtures/prism | 2 +- vscode/src/debugger.ts | 26 +++- vscode/src/rubyLsp.ts | 8 +- vscode/src/test/suite/client.test.ts | 12 +- vscode/src/test/suite/debugger.test.ts | 21 +-- vscode/src/test/suite/helpers.ts | 16 ++ vscode/src/test/suite/launch.test.ts | 12 +- vscode/src/test/suite/ruby.test.ts | 10 +- vscode/src/test/suite/ruby/asdf.test.ts | 17 +-- vscode/src/test/suite/ruby/chruby.test.ts | 11 +- vscode/src/test/suite/ruby/custom.test.ts | 11 +- vscode/src/test/suite/ruby/mise.test.ts | 15 +- vscode/src/test/suite/ruby/none.test.ts | 11 +- vscode/src/test/suite/ruby/rbenv.test.ts | 15 +- .../src/test/suite/ruby/rubyInstaller.test.ts | 11 +- vscode/src/test/suite/ruby/rvm.test.ts | 14 +- vscode/src/test/suite/ruby/shadowenv.test.ts | 14 +- vscode/src/test/suite/rubyLsp.test.ts | 143 ++++++++++++++++++ vscode/src/test/suite/testController.test.ts | 15 +- vscode/src/test/suite/workspace.test.ts | 10 +- 20 files changed, 232 insertions(+), 162 deletions(-) create mode 100644 vscode/src/test/suite/rubyLsp.test.ts diff --git a/test/fixtures/prism b/test/fixtures/prism index 2e9bf5e21..6f506e134 160000 --- a/test/fixtures/prism +++ b/test/fixtures/prism @@ -1 +1 @@ -Subproject commit 2e9bf5e21bf0fdfc0635bcfa6aff444ba64e6edd +Subproject commit 6f506e1348b3a0bd83c3b5a1b78fc1e98c2202d5 diff --git a/vscode/src/debugger.ts b/vscode/src/debugger.ts index b4ff8472d..bae227e9c 100644 --- a/vscode/src/debugger.ts +++ b/vscode/src/debugger.ts @@ -102,12 +102,24 @@ export class Debugger debugConfiguration: vscode.DebugConfiguration, _token?: vscode.CancellationToken, ): vscode.ProviderResult { - const workspace = this.workspaceResolver(folder?.uri); + // On certain occasions, the objects passed to this method are serialized. In particular for the URI, we have to + // ensure we're dealing with a `vscode.Uri` object and not a plain object + const uriAttributes = + folder?.uri ?? debugConfiguration.workspaceFolder?.uri; + + if (!uriAttributes) { + throw new Error(`Couldn't find a workspace to start debugging`); + } + + const uri = + uriAttributes instanceof vscode.Uri + ? uriAttributes + : vscode.Uri.from(uriAttributes); + + const workspace = this.workspaceResolver(uri); if (!workspace) { - throw new Error( - `Couldn't find a workspace for URI: ${folder?.uri} or editor: ${vscode.window.activeTextEditor}`, - ); + throw new Error(`Couldn't find a workspace for URI: ${uri}`); } if (debugConfiguration.env) { @@ -120,10 +132,8 @@ export class Debugger debugConfiguration.env = workspace.ruby.env; } - const workspaceUri = workspace.workspaceFolder.uri; - debugConfiguration.targetFolder = { - path: workspaceUri.fsPath, + path: uri.fsPath, name: workspace.workspaceFolder.name, }; @@ -133,7 +143,7 @@ export class Debugger return debugConfiguration; } - const customBundleUri = vscode.Uri.joinPath(workspaceUri, ".ruby-lsp"); + const customBundleUri = vscode.Uri.joinPath(uri, ".ruby-lsp"); return vscode.workspace.fs.readDirectory(customBundleUri).then( (value) => { diff --git a/vscode/src/rubyLsp.ts b/vscode/src/rubyLsp.ts index d119b7c4d..202071d65 100644 --- a/vscode/src/rubyLsp.ts +++ b/vscode/src/rubyLsp.ts @@ -176,6 +176,10 @@ export class RubyLsp { } } + getWorkspace(uri: vscode.Uri): Workspace | undefined { + return this.workspaces.get(uri.toString()); + } + private async activateWorkspace( workspaceFolder: vscode.WorkspaceFolder, eager: boolean, @@ -693,10 +697,6 @@ export class RubyLsp { return this.getWorkspace(workspaceFolder.uri); } - private getWorkspace(uri: vscode.Uri): Workspace | undefined { - return this.workspaces.get(uri.toString()); - } - private workspaceResolver( uri: vscode.Uri | undefined, ): Workspace | undefined { diff --git a/vscode/src/test/suite/client.test.ts b/vscode/src/test/suite/client.test.ts index c8cb1fb78..fde185baa 100644 --- a/vscode/src/test/suite/client.test.ts +++ b/vscode/src/test/suite/client.test.ts @@ -34,7 +34,7 @@ import { WorkspaceChannel } from "../../workspaceChannel"; import { MAJOR, MINOR } from "../rubyVersion"; import { FAKE_TELEMETRY, FakeLogger } from "./fakeTelemetry"; -import { createRubySymlinks } from "./helpers"; +import { createRubySymlinks, fakeContext } from "./helpers"; async function launchClient(workspaceUri: vscode.Uri) { const workspaceFolder: vscode.WorkspaceFolder = { @@ -43,15 +43,7 @@ async function launchClient(workspaceUri: vscode.Uri) { index: 0, }; - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.file(path.join(workspaceUri.fsPath, "vscode")), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const fakeLogger = new FakeLogger(); const outputChannel = new WorkspaceChannel("fake", fakeLogger as any); diff --git a/vscode/src/test/suite/debugger.test.ts b/vscode/src/test/suite/debugger.test.ts index dc0a7d237..a97fc3a31 100644 --- a/vscode/src/test/suite/debugger.test.ts +++ b/vscode/src/test/suite/debugger.test.ts @@ -14,7 +14,7 @@ import { LOG_CHANNEL, asyncExec } from "../../common"; import { RUBY_VERSION } from "../rubyVersion"; import { FAKE_TELEMETRY } from "./fakeTelemetry"; -import { createRubySymlinks } from "./helpers"; +import { createRubySymlinks, fakeContext } from "./helpers"; suite("Debugger", () => { const original = vscode.workspace @@ -34,7 +34,7 @@ suite("Debugger", () => { }); test("Provide debug configurations returns the default configs", () => { - const context = { subscriptions: [] } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const debug = new Debugger(context, () => { return undefined; }); @@ -69,7 +69,7 @@ suite("Debugger", () => { }); test("Resolve configuration injects Ruby environment", async () => { - const context = { subscriptions: [] } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const ruby = { env: { bogus: "hello!" } } as unknown as Ruby; const workspaceFolder = { name: "fake", @@ -99,7 +99,7 @@ suite("Debugger", () => { }); test("Resolve configuration injects Ruby environment and allows users custom environment", async () => { - const context = { subscriptions: [] } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const ruby = { env: { bogus: "hello!" } } as unknown as Ruby; const workspaceFolder = { name: "fake", @@ -134,7 +134,7 @@ suite("Debugger", () => { fs.mkdirSync(path.join(tmpPath, ".ruby-lsp")); fs.writeFileSync(path.join(tmpPath, ".ruby-lsp", "Gemfile"), "hello!"); - const context = { subscriptions: [] } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const ruby = { env: { bogus: "hello!" } } as unknown as Ruby; const workspaceFolder = { name: "fake", @@ -198,15 +198,7 @@ suite("Debugger", () => { 'source "https://rubygems.org"\ngem "debug"', ); - const extensionPath = path.dirname(path.dirname(path.dirname(__dirname))); - const context = { - subscriptions: [], - workspaceState: { - get: () => undefined, - update: () => undefined, - }, - extensionUri: vscode.Uri.file(extensionPath), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const outputChannel = new WorkspaceChannel("fake", LOG_CHANNEL); const workspaceFolder: vscode.WorkspaceFolder = { uri: vscode.Uri.file(tmpPath), @@ -247,6 +239,7 @@ suite("Debugger", () => { name: "Debug", request: "launch", program: `ruby ${path.join(tmpPath, "test.rb")}`, + workspaceFolder, }); } catch (error: any) { assert.fail(`Failed to launch debugger: ${error.message}`); diff --git a/vscode/src/test/suite/helpers.ts b/vscode/src/test/suite/helpers.ts index ade371649..fd1d4f026 100644 --- a/vscode/src/test/suite/helpers.ts +++ b/vscode/src/test/suite/helpers.ts @@ -3,6 +3,8 @@ import path from "path"; import os from "os"; import fs from "fs"; +import * as vscode from "vscode"; + import { MAJOR, MINOR, RUBY_VERSION } from "../rubyVersion"; export function createRubySymlinks() { @@ -41,3 +43,17 @@ export function createRubySymlinks() { } } } + +export function fakeContext(): vscode.ExtensionContext { + return { + extensionMode: vscode.ExtensionMode.Test, + subscriptions: [], + workspaceState: { + get: (_name: string) => undefined, + update: (_name: string, _value: any) => Promise.resolve(), + }, + extensionUri: vscode.Uri.file( + path.dirname(path.dirname(path.dirname(__dirname))), + ), + } as unknown as vscode.ExtensionContext; +} diff --git a/vscode/src/test/suite/launch.test.ts b/vscode/src/test/suite/launch.test.ts index 1fc28f85f..99d3a44fb 100644 --- a/vscode/src/test/suite/launch.test.ts +++ b/vscode/src/test/suite/launch.test.ts @@ -14,7 +14,7 @@ import { WorkspaceChannel } from "../../workspaceChannel"; import * as common from "../../common"; import { FAKE_TELEMETRY, FakeLogger } from "./fakeTelemetry"; -import { createRubySymlinks } from "./helpers"; +import { createRubySymlinks, fakeContext } from "./helpers"; suite("Launch integrations", () => { const workspacePath = path.dirname( @@ -27,15 +27,7 @@ suite("Launch integrations", () => { index: 0, }; - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.joinPath(workspaceUri, "vscode"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const fakeLogger = new FakeLogger(); const outputChannel = new WorkspaceChannel("fake", fakeLogger as any); diff --git a/vscode/src/test/suite/ruby.test.ts b/vscode/src/test/suite/ruby.test.ts index dbe6a8644..5c7b1bade 100644 --- a/vscode/src/test/suite/ruby.test.ts +++ b/vscode/src/test/suite/ruby.test.ts @@ -17,6 +17,7 @@ import { } from "../../ruby/versionManager"; import { FAKE_TELEMETRY } from "./fakeTelemetry"; +import { fakeContext } from "./helpers"; suite("Ruby environment activation", () => { const workspacePath = path.dirname( @@ -27,14 +28,7 @@ suite("Ruby environment activation", () => { name: path.basename(workspacePath), index: 0, }; - const context = { - extensionMode: vscode.ExtensionMode.Test, - workspaceState: { - get: () => undefined, - update: () => undefined, - }, - extensionUri: vscode.Uri.file(path.join(workspacePath, "vscode")), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const outputChannel = new WorkspaceChannel("fake", LOG_CHANNEL); test("Activate fetches Ruby information when outside of Ruby LSP", async () => { diff --git a/vscode/src/test/suite/ruby/asdf.test.ts b/vscode/src/test/suite/ruby/asdf.test.ts index f03649e3d..8e8e761aa 100644 --- a/vscode/src/test/suite/ruby/asdf.test.ts +++ b/vscode/src/test/suite/ruby/asdf.test.ts @@ -13,6 +13,7 @@ import { FIELD_SEPARATOR, VALUE_SEPARATOR, } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("Asdf", () => { if (os.platform() === "win32") { @@ -20,15 +21,7 @@ suite("Asdf", () => { console.log("Skipping Asdf tests on Windows"); return; } - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); test("Finds Ruby based on .tool-versions", async () => { // eslint-disable-next-line no-process-env @@ -63,10 +56,11 @@ suite("Asdf", () => { const shellStub = sinon.stub(vscode.env, "shell").get(() => "/bin/bash"); const { env, version, yjit } = await asdf.activate(); + const baseCommand = `. ${os.homedir()}/.asdf/asdf.sh && asdf exec ruby`; assert.ok( execStub.calledOnceWithExactly( - `. ${os.homedir()}/.asdf/asdf.sh && asdf exec ruby -EUTF-8:UTF-8 '/fake/activation.rb'`, + `${baseCommand} -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: "/bin/bash", @@ -121,10 +115,11 @@ suite("Asdf", () => { .get(() => "/opt/homebrew/bin/fish"); const { env, version, yjit } = await asdf.activate(); + const baseCommand = `. ${os.homedir()}/.asdf/asdf.fish && asdf exec ruby`; assert.ok( execStub.calledOnceWithExactly( - `. ${os.homedir()}/.asdf/asdf.fish && asdf exec ruby -EUTF-8:UTF-8 '/fake/activation.rb'`, + `${baseCommand} -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: "/opt/homebrew/bin/fish", diff --git a/vscode/src/test/suite/ruby/chruby.test.ts b/vscode/src/test/suite/ruby/chruby.test.ts index 1b3268325..faa41c6ca 100644 --- a/vscode/src/test/suite/ruby/chruby.test.ts +++ b/vscode/src/test/suite/ruby/chruby.test.ts @@ -13,6 +13,7 @@ import { WorkspaceChannel } from "../../../workspaceChannel"; import { LOG_CHANNEL } from "../../../common"; import { RUBY_VERSION, MAJOR, MINOR, VERSION_REGEX } from "../../rubyVersion"; import { ActivationResult } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; // Create links to the real Ruby installations on CI and on our local machines function createRubySymlinks(destination: string) { @@ -50,15 +51,7 @@ suite("Chruby", () => { return; } - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); let rootPath: string; let workspacePath: string; diff --git a/vscode/src/test/suite/ruby/custom.test.ts b/vscode/src/test/suite/ruby/custom.test.ts index 42dadc64f..7612c4b31 100644 --- a/vscode/src/test/suite/ruby/custom.test.ts +++ b/vscode/src/test/suite/ruby/custom.test.ts @@ -14,17 +14,10 @@ import { FIELD_SEPARATOR, VALUE_SEPARATOR, } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("Custom", () => { - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); test("Invokes custom script and then Ruby", async () => { const workspacePath = fs.mkdtempSync( diff --git a/vscode/src/test/suite/ruby/mise.test.ts b/vscode/src/test/suite/ruby/mise.test.ts index 7fade58f0..d9fd2b978 100644 --- a/vscode/src/test/suite/ruby/mise.test.ts +++ b/vscode/src/test/suite/ruby/mise.test.ts @@ -14,6 +14,7 @@ import { FIELD_SEPARATOR, VALUE_SEPARATOR, } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("Mise", () => { if (os.platform() === "win32") { @@ -22,15 +23,7 @@ suite("Mise", () => { return; } - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); test("Finds Ruby only binary path is appended to PATH", async () => { // eslint-disable-next-line no-process-env @@ -74,7 +67,7 @@ suite("Mise", () => { assert.ok( execStub.calledOnceWithExactly( - `${os.homedir()}/.local/bin/mise x -- ruby -EUTF-8:UTF-8 '/fake/activation.rb'`, + `${os.homedir()}/.local/bin/mise x -- ruby -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: vscode.env.shell, @@ -140,7 +133,7 @@ suite("Mise", () => { assert.ok( execStub.calledOnceWithExactly( - `${misePath} x -- ruby -EUTF-8:UTF-8 '/fake/activation.rb'`, + `${misePath} x -- ruby -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: vscode.env.shell, diff --git a/vscode/src/test/suite/ruby/none.test.ts b/vscode/src/test/suite/ruby/none.test.ts index f3cbd5aea..34ec1d54b 100644 --- a/vscode/src/test/suite/ruby/none.test.ts +++ b/vscode/src/test/suite/ruby/none.test.ts @@ -14,18 +14,11 @@ import { FIELD_SEPARATOR, VALUE_SEPARATOR, } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("None", () => { test("Invokes Ruby directly", async () => { - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); const workspacePath = fs.mkdtempSync( path.join(os.tmpdir(), "ruby-lsp-test-"), ); diff --git a/vscode/src/test/suite/ruby/rbenv.test.ts b/vscode/src/test/suite/ruby/rbenv.test.ts index a0d4d30ed..2581bdd8d 100644 --- a/vscode/src/test/suite/ruby/rbenv.test.ts +++ b/vscode/src/test/suite/ruby/rbenv.test.ts @@ -14,6 +14,7 @@ import { FIELD_SEPARATOR, VALUE_SEPARATOR, } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("Rbenv", () => { if (os.platform() === "win32") { @@ -22,15 +23,7 @@ suite("Rbenv", () => { return; } - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); test("Finds Ruby based on .ruby-version", async () => { // eslint-disable-next-line no-process-env @@ -64,7 +57,7 @@ suite("Rbenv", () => { assert.ok( execStub.calledOnceWithExactly( - `rbenv exec ruby -EUTF-8:UTF-8 '/fake/activation.rb'`, + `rbenv exec ruby -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: vscode.env.shell, @@ -128,7 +121,7 @@ suite("Rbenv", () => { assert.ok( execStub.calledOnceWithExactly( - `${rbenvPath} exec ruby -EUTF-8:UTF-8 '/fake/activation.rb'`, + `${rbenvPath} exec ruby -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: vscode.env.shell, diff --git a/vscode/src/test/suite/ruby/rubyInstaller.test.ts b/vscode/src/test/suite/ruby/rubyInstaller.test.ts index 01bd47029..dbee3007c 100644 --- a/vscode/src/test/suite/ruby/rubyInstaller.test.ts +++ b/vscode/src/test/suite/ruby/rubyInstaller.test.ts @@ -13,6 +13,7 @@ import { WorkspaceChannel } from "../../../workspaceChannel"; import { LOG_CHANNEL } from "../../../common"; import { RUBY_VERSION, VERSION_REGEX } from "../../rubyVersion"; import { ACTIVATION_SEPARATOR } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("RubyInstaller", () => { if (os.platform() !== "win32") { @@ -21,15 +22,7 @@ suite("RubyInstaller", () => { return; } - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); let rootPath: string; let workspacePath: string; diff --git a/vscode/src/test/suite/ruby/rvm.test.ts b/vscode/src/test/suite/ruby/rvm.test.ts index e1526b02e..77532c3cd 100644 --- a/vscode/src/test/suite/ruby/rvm.test.ts +++ b/vscode/src/test/suite/ruby/rvm.test.ts @@ -14,6 +14,7 @@ import { FIELD_SEPARATOR, VALUE_SEPARATOR, } from "../../../ruby/versionManager"; +import { fakeContext } from "../helpers"; suite("RVM", () => { if (os.platform() === "win32") { @@ -22,15 +23,7 @@ suite("RVM", () => { return; } - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.parse("file:///fake"), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); test("Populates the gem env and path", async () => { const workspacePath = process.env.PWD!; @@ -71,10 +64,11 @@ suite("RVM", () => { }); const { env, version, yjit } = await rvm.activate(); + const baseComamnd = path.join(os.homedir(), ".rvm", "bin", "rvm-auto-ruby"); assert.ok( execStub.calledOnceWithExactly( - `${path.join(os.homedir(), ".rvm", "bin", "rvm-auto-ruby")} -EUTF-8:UTF-8 '/fake/activation.rb'`, + `${baseComamnd} -EUTF-8:UTF-8 '${context.extensionUri.fsPath}/activation.rb'`, { cwd: workspacePath, shell: vscode.env.shell, diff --git a/vscode/src/test/suite/ruby/shadowenv.test.ts b/vscode/src/test/suite/ruby/shadowenv.test.ts index ace84d4bf..080474e0d 100644 --- a/vscode/src/test/suite/ruby/shadowenv.test.ts +++ b/vscode/src/test/suite/ruby/shadowenv.test.ts @@ -14,6 +14,7 @@ import { WorkspaceChannel } from "../../../workspaceChannel"; import { LOG_CHANNEL, asyncExec } from "../../../common"; import { RUBY_VERSION } from "../../rubyVersion"; import * as common from "../../../common"; +import { fakeContext } from "../helpers"; suite("Shadowenv", () => { if (os.platform() === "win32") { @@ -30,18 +31,7 @@ suite("Shadowenv", () => { return; } - const extensionPath = path.dirname( - path.dirname(path.dirname(path.dirname(path.dirname(__dirname)))), - ); - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - extensionUri: vscode.Uri.file(path.join(extensionPath, "vscode")), - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); let rootPath: string; let workspacePath: string; diff --git a/vscode/src/test/suite/rubyLsp.test.ts b/vscode/src/test/suite/rubyLsp.test.ts new file mode 100644 index 000000000..c818f6972 --- /dev/null +++ b/vscode/src/test/suite/rubyLsp.test.ts @@ -0,0 +1,143 @@ +/* eslint-disable no-process-env */ +import path from "path"; +import assert from "assert"; +import fs from "fs"; +import os from "os"; + +import sinon from "sinon"; +import * as vscode from "vscode"; +import { beforeEach, afterEach } from "mocha"; + +import { RubyLsp } from "../../rubyLsp"; +import { RUBY_VERSION } from "../rubyVersion"; +import { ManagerIdentifier } from "../../ruby"; + +import { FAKE_TELEMETRY } from "./fakeTelemetry"; +import { fakeContext } from "./helpers"; + +suite("Ruby LSP", () => { + const context = fakeContext(); + let workspacePath: string; + let workspaceUri: vscode.Uri; + let workspaceFolder: vscode.WorkspaceFolder; + const originalSaveBeforeStart = vscode.workspace + .getConfiguration("debug") + .get("saveBeforeStart"); + let workspacesStub: sinon.SinonStub; + + beforeEach(async () => { + await vscode.workspace + .getConfiguration("debug") + .update("saveBeforeStart", "none", true); + workspacePath = fs.mkdtempSync( + path.join(os.tmpdir(), "ruby-lsp-integration-test-"), + ); + workspaceUri = vscode.Uri.file(workspacePath); + workspaceFolder = { + uri: workspaceUri, + name: path.basename(workspacePath), + index: 0, + }; + + workspacesStub = sinon + .stub(vscode.workspace, "workspaceFolders") + .get(() => [workspaceFolder]); + }); + + afterEach(async () => { + workspacesStub.restore(); + fs.rmSync(workspacePath, { recursive: true, force: true }); + + await vscode.workspace + .getConfiguration("debug") + .update("saveBeforeStart", originalSaveBeforeStart, true); + }); + + function writeFileSetup() { + fs.writeFileSync(path.join(workspacePath, "test.rb"), "1 + 1"); + fs.writeFileSync(path.join(workspacePath, ".ruby-version"), RUBY_VERSION); + fs.writeFileSync( + path.join(workspacePath, "Gemfile"), + 'source "https://rubygems.org"\n', + ); + fs.writeFileSync( + path.join(workspacePath, "Gemfile.lock"), + [ + "GEM", + " remote: https://rubygems.org/", + " specs:", + "", + "PLATFORMS", + " arm64-darwin-23", + " ruby", + "", + "DEPENDENCIES", + "", + "BUNDLED WITH", + " 2.5.16", + ].join("\n"), + ); + fs.mkdirSync(path.join(workspacePath, ".bundle")); + fs.writeFileSync( + path.join(workspacePath, ".bundle", "config"), + `BUNDLE_PATH: ${path.join("vendor", "bundle")}`, + ); + } + + test("launching debugger in a project with local Bundler settings and composed bundle", async () => { + writeFileSetup(); + + if (process.env.CI) { + await vscode.workspace + .getConfiguration("rubyLsp") + .update( + "rubyVersionManager", + { identifier: ManagerIdentifier.None }, + true, + ); + } + + const rubyLsp = new RubyLsp(context, FAKE_TELEMETRY); + + try { + await rubyLsp.activate(); + } catch (error: any) { + assert.fail( + `Failed to activate Ruby LSP: ${error.message}\n\n${error.stack}`, + ); + } + + // Verify that the composed environment was properly merged into the Ruby object + const workspace = rubyLsp.getWorkspace(workspaceFolder.uri)!; + assert.match(workspace.ruby.env.BUNDLE_PATH!, /vendor(\/|\\)bundle/); + assert.match( + workspace.ruby.env.BUNDLE_GEMFILE!, + /\.ruby-lsp(\/|\\)Gemfile/, + ); + + try { + await vscode.debug.startDebugging(workspaceFolder, { + type: "ruby_lsp", + name: "Debug", + request: "launch", + program: `ruby ${path.join(workspacePath, "test.rb")}`, + workspaceFolder, + }); + } catch (error: any) { + assert.fail(`Failed to launch debugger: ${error.message}`); + } + + await new Promise((resolve) => { + const callback = vscode.debug.onDidTerminateDebugSession((_session) => { + context.subscriptions.forEach((subscription) => { + if (!("logLevel" in subscription)) { + subscription.dispose(); + } + }); + + callback.dispose(); + resolve(); + }); + }); + }).timeout(90000); +}); diff --git a/vscode/src/test/suite/testController.test.ts b/vscode/src/test/suite/testController.test.ts index 29b198768..2f9462419 100644 --- a/vscode/src/test/suite/testController.test.ts +++ b/vscode/src/test/suite/testController.test.ts @@ -2,21 +2,20 @@ import * as assert from "assert"; import * as vscode from "vscode"; import { CodeLens } from "vscode-languageclient/node"; +import { afterEach } from "mocha"; import { TestController } from "../../testController"; import { Command } from "../../common"; import { FAKE_TELEMETRY } from "./fakeTelemetry"; +import { fakeContext } from "./helpers"; suite("TestController", () => { - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); + + afterEach(() => { + context.subscriptions.forEach((subscription) => subscription.dispose()); + }); test("createTestItems doesn't break when there's a missing group", () => { const controller = new TestController( diff --git a/vscode/src/test/suite/workspace.test.ts b/vscode/src/test/suite/workspace.test.ts index ed91e2619..bbafd8772 100644 --- a/vscode/src/test/suite/workspace.test.ts +++ b/vscode/src/test/suite/workspace.test.ts @@ -11,16 +11,10 @@ import { beforeEach, afterEach } from "mocha"; import { Workspace } from "../../workspace"; import { FAKE_TELEMETRY } from "./fakeTelemetry"; +import { fakeContext } from "./helpers"; suite("Workspace", () => { - const context = { - extensionMode: vscode.ExtensionMode.Test, - subscriptions: [], - workspaceState: { - get: (_name: string) => undefined, - update: (_name: string, _value: any) => Promise.resolve(), - }, - } as unknown as vscode.ExtensionContext; + const context = fakeContext(); let workspacePath: string; let workspaceUri: vscode.Uri; let workspaceFolder: vscode.WorkspaceFolder;