local a = require "mason-core.async" local match = require "luassert.match" local platform = require "mason-core.platform" local process = require "mason-core.process" local spawn = require "mason-core.spawn" local spy = require "luassert.spy" local stub = require "luassert.stub" describe("async spawn", function() local snapshot before_each(function() snapshot = assert.snapshot() end) after_each(function() snapshot:revert() end) it("should spawn commands and return stdout & stderr", function() local result = a.run_blocking(spawn.env, { env_raw = { "FOO=bar" }, }) assert.is_true(result:is_success()) assert.equals("FOO=bar\n", result:get_or_nil().stdout) assert.equals("", result:get_or_nil().stderr) end) it("should use provided stdio_sink", function() local stdout = spy.new() local stdio = process.StdioSink:new { stdout = stdout, } local result = a.run_blocking(spawn.env, { env_raw = { "FOO=bar" }, stdio_sink = stdio, }) assert.is_true(result:is_success()) assert.equals(nil, result:get_or_nil()) -- Not 100 %guaranteed it's only called once because output is always buffered, but it's extremely likely assert.spy(stdout).was_called(1) assert.spy(stdout).was_called_with "FOO=bar\n" end) it("should pass command arguments", function() local result = a.run_blocking(spawn.bash, { "-c", 'echo "Hello $VAR"', env = { VAR = "world" }, }) assert.is_true(result:is_success()) assert.equals("Hello world\n", result:get_or_nil().stdout) assert.equals("", result:get_or_nil().stderr) end) it("should ignore vim.NIL args", function() spy.on(process, "spawn") local result = a.run_blocking(spawn.bash, { vim.NIL, vim.NIL, "-c", { vim.NIL, vim.NIL }, 'echo "Hello $VAR"', env = { VAR = "world" }, }) assert.is_true(result:is_success()) assert.equals("Hello world\n", result:get_or_nil().stdout) assert.equals("", result:get_or_nil().stderr) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( "bash", match.tbl_containing { stdio_sink = match.instanceof(process.BufferedSink), env = match.list_containing "VAR=world", args = match.tbl_containing { "-c", 'echo "Hello $VAR"', }, }, match.is_function() ) end) it("should flatten table args", function() local result = a.run_blocking(spawn.bash, { { "-c", 'echo "Hello $VAR"' }, env = { VAR = "world" }, }) assert.is_true(result:is_success()) assert.equals("Hello world\n", result:get_or_nil().stdout) assert.equals("", result:get_or_nil().stderr) end) it("should call on_spawn", function() local on_spawn = spy.new(function(_, stdio) local stdin = stdio[1] stdin:write "im so piped rn" stdin:close() end) local result = a.run_blocking(spawn.cat, { { "-" }, on_spawn = on_spawn, }) assert.spy(on_spawn).was_called(1) assert.spy(on_spawn).was_called_with(match.is_not.is_nil(), match.is_table(), match.is_number()) assert.is_true(result:is_success()) assert.equals("im so piped rn", result:get_or_nil().stdout) end) it("should not call on_spawn if spawning fails", function() local on_spawn = spy.new() local result = a.run_blocking(spawn.this_cmd_doesnt_exist, { on_spawn = on_spawn, }) assert.spy(on_spawn).was_called(0) assert.is_true(result:is_failure()) end) it("should handle failure to spawn process", function() stub(process, "spawn", function(_, _, callback) callback(false) end) local result = a.run_blocking(spawn.my_cmd, {}) assert.is_true(result:is_failure()) assert.is_nil(result:err_or_nil().exit_code) end) it("should format failure message", function() stub(process, "spawn", function(cmd, opts, callback) opts.stdio_sink:stderr(("This is an error message for %s!"):format(cmd)) callback(false, 127) end) local result = a.run_blocking(spawn.bash, {}) assert.is_true(result:is_failure()) assert.equals( "spawn: bash failed with exit code 127 and signal -. This is an error message for bash!", tostring(result:err_or_nil()) ) end) describe("Windows", function() -- Note: Tests assume they're executed in a Unix environment (e.g. uses Unix path separators in tests). before_each(function() platform.is.win = true end) after_each(function() platform.is.win = nil end) it("should use exepath to get absolute path to executable", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) local result = a.run_blocking(spawn.bash, { "arg1" }) assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( vim.fn.exepath "bash", match.tbl_containing { args = match.same { "arg1" }, }, match.is_function() ) end) it("should use exepath if env.PATH is set", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) local result = a.run_blocking(spawn.bash, { "arg1", env = { PATH = "C:\\some\\path:" .. vim.env.PATH } }) assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( vim.fn.exepath "bash", match.tbl_containing { args = match.same { "arg1" }, env = match.is_table(), }, match.is_function() ) end) it("should use exepath if env_raw.PATH is set", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) local result = a.run_blocking(spawn.bash, { "arg1", env_raw = { "PATH=C:\\some\\path:" .. vim.env.PATH } }) assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( vim.fn.exepath "bash", match.tbl_containing { args = match.same { "arg1" }, env = match.is_table(), }, match.is_function() ) end) it("should default to provided cmd if exepath returns nothing", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) local result = a.run_blocking(spawn.bash, { "arg1", env_raw = { "PATH=C:\\some\\path" } }) assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( "bash", match.tbl_containing { args = match.same { "arg1" }, }, match.is_function() ) end) it("should use exepath if with_paths is provided", function() stub(process, "spawn", function(_, _, callback) callback(true, 0, 0) end) local result = a.run_blocking(spawn.bash, { "arg1", with_paths = { "C:\\some\\path" } }) assert.is_true(result:is_success()) assert.spy(process.spawn).was_called(1) assert.spy(process.spawn).was_called_with( vim.fn.exepath "bash", match.tbl_containing { args = match.same { "arg1" }, }, match.is_function() ) end) end) end)