diff --git a/lib/spoom/context/exec.rb b/lib/spoom/context/exec.rb index db3e46cd..b0ac3ab7 100644 --- a/lib/spoom/context/exec.rb +++ b/lib/spoom/context/exec.rb @@ -1,6 +1,8 @@ # typed: strict # frozen_string_literal: true +require "shellwords" + module Spoom class ExecResult < T::Struct const :out, String @@ -30,11 +32,21 @@ def exec(command, capture_err: true) Bundler.with_unbundled_env do opts = { chdir: absolute_path } #: Hash[Symbol, untyped] + # When Ruby is wrapped in another dev environment (e.g. Nix), that environment might set + # env variables that cause Sorbet (or other tools) to link to incorrect versions of + # dependencies. + # + # In the case of Sorbet, this can lead to corrupted JSON output. + # + # Executing the command directly through the shell bypasses any Ruby wrappers and + # ensures that we are using the versions of tools that are default on the system. + command_with_shell = "/bin/sh -c #{command.shellescape}" + if capture_err - out, err, status = Open3.capture3(command, opts) + out, err, status = Open3.capture3(command_with_shell, opts) ExecResult.new(out: out, err: err, status: T.must(status.success?), exit_code: T.must(status.exitstatus)) else - out, status = Open3.capture2(command, opts) + out, status = Open3.capture2(command_with_shell, opts) ExecResult.new(out: out, err: nil, status: T.must(status.success?), exit_code: T.must(status.exitstatus)) end end diff --git a/test/spoom/context/exec_test.rb b/test/spoom/context/exec_test.rb index 358b6604..6aeaa020 100644 --- a/test/spoom/context/exec_test.rb +++ b/test/spoom/context/exec_test.rb @@ -9,9 +9,11 @@ class ExecTest < Minitest::Test def test_context_exec context = Context.mktmp! - assert_raises(Errno::ENOENT) do - context.exec("command/not/found") - end + # Test that unknown commands return a failed status (not raise an exception) + # due to shell wrapping in exec method + res = context.exec("command_that_does_not_exist") + refute(res.status) + refute_empty(res.err) res = context.exec("echo 'Hello, world!'") assert_equal("Hello, world!\n", res.out)