Skip to content

Commit

Permalink
Warn users if shadownev is not installed
Browse files Browse the repository at this point in the history
  • Loading branch information
vinistock committed Aug 27, 2024
1 parent 1891baa commit c9934dd
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 78 deletions.
45 changes: 34 additions & 11 deletions vscode/src/ruby/shadowenv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,41 @@ export class Shadowenv extends VersionManager {
version: parsedResult.version,
};
} catch (error: any) {
// If running `shadowev exec` fails, it's typically because the workspace has not been trusted yet. Here we offer
// to trust it and fail it the user decides to not the trust the workspace (since in that case, we are unable to
// activate the Ruby environment).
const answer = await vscode.window.showErrorMessage(
`Failed to run shadowenv exec. Is ${this.bundleUri.fsPath} trusted? Run 'shadowenv trust --help' to know more`,
"Trust workspace",
"Cancel",
);
const message = error.message;

// Depending on the user's shell, the message for not finding the executable is slightly different. For example,
// in zsh, it's "zsh: command not found: shadowenv", but in bash, it's "bash: shadowenv: command not found". Here
// we just try to figure out if the command not being there caused the error
if (
message &&
message.includes("command") &&
message.includes("shadowenv")
) {
const answer = await vscode.window.showErrorMessage(
`Couldn't find shadowenv executable. Double-check that it's installed and that it's in your PATH.`,
"Reload window",
"Cancel",
);

if (answer === "Reload window") {
return vscode.commands.executeCommand(
"workbench.action.reloadWindow",
);
}
} else {
// If running `shadowev exec` fails, it's typically because the workspace has not been trusted yet. Here we
// offer to trust it and fail it the user decides to not the trust the workspace (since in that case, we are
// unable to activate the Ruby environment).
const answer = await vscode.window.showErrorMessage(
`Failed to run shadowenv. Is ${this.bundleUri.fsPath} trusted? Run 'shadowenv trust --help' to know more`,
"Trust workspace",
"Cancel",
);

if (answer === "Trust workspace") {
await asyncExec("shadowenv trust", { cwd: this.bundleUri.fsPath });
return this.activate();
if (answer === "Trust workspace") {
await asyncExec("shadowenv trust", { cwd: this.bundleUri.fsPath });
return this.activate();
}
}

throw new Error(
Expand Down
146 changes: 79 additions & 67 deletions vscode/src/test/suite/ruby/shadowenv.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Shadowenv } from "../../../ruby/shadowenv";
import { WorkspaceChannel } from "../../../workspaceChannel";
import { LOG_CHANNEL, asyncExec } from "../../../common";
import { RUBY_VERSION } from "../../rubyVersion";
import * as common from "../../../common";

suite("Shadowenv", () => {
if (os.platform() === "win32") {
Expand All @@ -24,8 +25,8 @@ suite("Shadowenv", () => {
let workspacePath: string;
let workspaceFolder: vscode.WorkspaceFolder;
let outputChannel: WorkspaceChannel;
let bundleGemfileStub: sinon.SinonStub;
let rubyBinPath: string;
const [major, minor, patch] = RUBY_VERSION.split(".");

if (process.env.CI && os.platform() === "linux") {
rubyBinPath = path.join(
Expand Down Expand Up @@ -57,17 +58,49 @@ suite("Shadowenv", () => {
`Ruby bin path does not exist ${rubyBinPath}`,
);

const shadowLispFile = `
(provide "ruby" "${RUBY_VERSION}")
(when-let ((ruby-root (env/get "RUBY_ROOT")))
(env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
(when-let ((gem-root (env/get "GEM_ROOT")))
(env/remove-from-pathlist "PATH" (path-concat gem-root "bin")))
(when-let ((gem-home (env/get "GEM_HOME")))
(env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))))
(env/set "BUNDLE_PATH" ())
(env/set "GEM_PATH" ())
(env/set "GEM_HOME" ())
(env/set "RUBYOPT" ())
(env/set "RUBYLIB" ())
(env/set "RUBY_ROOT" "${path.dirname(rubyBinPath)}")
(env/prepend-to-pathlist "PATH" "${rubyBinPath}")
(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "${RUBY_VERSION}")
(env/set "GEM_ROOT" "${path.dirname(rubyBinPath)}/lib/ruby/gems/${major}.${minor}.0")
(when-let ((gem-root (env/get "GEM_ROOT")))
(env/prepend-to-pathlist "GEM_PATH" gem-root)
(env/prepend-to-pathlist "PATH" (path-concat gem-root "bin")))
(let ((gem-home
(path-concat (env/get "HOME") ".gem" (env/get "RUBY_ENGINE") "${RUBY_VERSION}")))
(do
(env/set "GEM_HOME" gem-home)
(env/prepend-to-pathlist "GEM_PATH" gem-home)
(env/prepend-to-pathlist "PATH" (path-concat gem-home "bin"))))
`;

beforeEach(() => {
rootPath = fs.mkdtempSync(path.join(os.tmpdir(), "ruby-lsp-test-chruby-"));
rootPath = fs.mkdtempSync(
path.join(os.tmpdir(), "ruby-lsp-test-shadowenv-"),
);
workspacePath = path.join(rootPath, "workspace");

fs.mkdirSync(workspacePath);
fs.mkdirSync(path.join(workspacePath, ".shadowenv.d"));

bundleGemfileStub = sinon
.stub(vscode.workspace, "getConfiguration")
.returns({ get: () => path.join(workspacePath, "Gemfile") } as any)!;

workspaceFolder = {
uri: vscode.Uri.from({ scheme: "file", path: workspacePath }),
name: path.basename(workspacePath),
Expand All @@ -77,8 +110,7 @@ suite("Shadowenv", () => {
});

afterEach(() => {
fs.rmSync(rootPath, { recursive: true, force: true });
bundleGemfileStub.restore();
// fs.rmSync(rootPath, { recursive: true, force: true });
});

test("Finds Ruby only binary path is appended to PATH", async () => {
Expand All @@ -102,26 +134,7 @@ suite("Shadowenv", () => {

fs.writeFileSync(
path.join(workspacePath, ".shadowenv.d", "500_ruby.lisp"),
`(provide "ruby" "${RUBY_VERSION}")
(when-let ((ruby-root (env/get "RUBY_ROOT")))
(env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
(when-let ((gem-root (env/get "GEM_ROOT")))
(env/remove-from-pathlist "PATH" (path-concat gem-root "bin")))
(when-let ((gem-home (env/get "GEM_HOME")))
(env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))))
(env/set "BUNDLE_PATH" ())
(env/set "GEM_PATH" ())
(env/set "GEM_HOME" ())
(env/set "RUBYOPT" ())
(env/set "RUBYLIB" ())
(env/set "RUBY_ROOT" "${path.dirname(rubyBinPath)}")
(env/prepend-to-pathlist "PATH" "${rubyBinPath}")
(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "${RUBY_VERSION}")
(env/set "GEM_ROOT" "${path.dirname(rubyBinPath)}/lib/ruby/gems/${RUBY_VERSION}")
`,
shadowLispFile,
);

const shadowenv = new Shadowenv(workspaceFolder, outputChannel);
Expand All @@ -130,45 +143,16 @@ suite("Shadowenv", () => {
assert.match(env.PATH!, new RegExp(rubyBinPath));
assert.strictEqual(
env.GEM_ROOT,
`${path.dirname(rubyBinPath)}/lib/ruby/gems/${RUBY_VERSION}`,
);
assert.strictEqual(version, RUBY_VERSION);
assert.notStrictEqual(yjit, undefined);
});

test("Overrides GEM_HOME and GEM_PATH if necessary", async () => {
await asyncExec("shadowenv trust", { cwd: workspacePath });

fs.writeFileSync(
path.join(workspacePath, ".shadowenv.d", "500_ruby.lisp"),
`(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "${RUBY_VERSION}")
(env/set "GEM_HOME" "/fake/.bundle/project/${RUBY_VERSION}")
(env/set "GEM_PATH" "/fake/.bundle/project/${RUBY_VERSION}:")
(env/prepend-to-pathlist "PATH" "/fake/.bundle/project/${RUBY_VERSION}/bin")
(env/prepend-to-pathlist "PATH" "${rubyBinPath}")`,
`${path.dirname(rubyBinPath)}/lib/ruby/gems/${major}.${minor}.0`,
);

const shadowenv = new Shadowenv(workspaceFolder, outputChannel);
const { env, version, yjit } = await shadowenv.activate();

assert.match(env.PATH!, new RegExp(rubyBinPath));
assert.strictEqual(env.GEM_HOME, `/fake/.bundle/project/${RUBY_VERSION}`);
assert.strictEqual(version, RUBY_VERSION);
assert.notStrictEqual(yjit, undefined);
});

test("Untrusted workspace offers to trust it", async () => {
fs.writeFileSync(
path.join(workspacePath, ".shadowenv.d", "500_ruby.lisp"),
`(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "${RUBY_VERSION}")
(env/set "GEM_HOME" "/fake/.bundle/project/${RUBY_VERSION}")
(env/set "GEM_PATH" "/fake/.bundle/project/${RUBY_VERSION}:")
(env/prepend-to-pathlist "PATH" "/fake/.bundle/project/${RUBY_VERSION}/bin")
(env/prepend-to-pathlist "PATH" "${rubyBinPath}")`,
shadowLispFile,
);

const stub = sinon
Expand All @@ -179,7 +163,10 @@ suite("Shadowenv", () => {
const { env, version, yjit } = await shadowenv.activate();

assert.match(env.PATH!, new RegExp(rubyBinPath));
assert.strictEqual(env.GEM_HOME, `/fake/.bundle/project/${RUBY_VERSION}`);
assert.match(
env.GEM_HOME!,
new RegExp(`\\.gem\\/ruby\\/${major}\\.${minor}\\.${patch}`),
);
assert.strictEqual(version, RUBY_VERSION);
assert.notStrictEqual(yjit, undefined);

Expand All @@ -191,13 +178,7 @@ suite("Shadowenv", () => {
test("Deciding not to trust the workspace fails activation", async () => {
fs.writeFileSync(
path.join(workspacePath, ".shadowenv.d", "500_ruby.lisp"),
`(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "${RUBY_VERSION}")
(env/set "GEM_HOME" "/fake/.bundle/project/${RUBY_VERSION}")
(env/set "GEM_PATH" "/fake/.bundle/project/${RUBY_VERSION}:")
(env/prepend-to-pathlist "PATH" "/fake/.bundle/project/${RUBY_VERSION}/bin")
(env/prepend-to-pathlist "PATH" "${rubyBinPath}")`,
shadowLispFile,
);

const stub = sinon
Expand All @@ -214,4 +195,35 @@ suite("Shadowenv", () => {

stub.restore();
});

test("Warns user is shadowenv executable can't be found", async () => {
await asyncExec("shadowenv trust", { cwd: workspacePath });

fs.writeFileSync(
path.join(workspacePath, ".shadowenv.d", "500_ruby.lisp"),
shadowLispFile,
);

const shadowenv = new Shadowenv(workspaceFolder, outputChannel);

const execStub = sinon
.stub(common, "asyncExec")
.rejects(new Error("shadowenv: command not found"));
const windowStub = sinon
.stub(vscode.window, "showErrorMessage")
.resolves("Cancel" as any);

await assert.rejects(async () => {
await shadowenv.activate();
});

assert.ok(
windowStub.calledOnceWith(
"Couldn't find shadowenv executable. Double-check that it's installed and that it's in your PATH.",
),
);

execStub.restore();
windowStub.restore();
});
});

0 comments on commit c9934dd

Please sign in to comment.