diff --git a/lua/flutter-tools/devices.lua b/lua/flutter-tools/devices.lua index bb5f005..91fdc21 100644 --- a/lua/flutter-tools/devices.lua +++ b/lua/flutter-tools/devices.lua @@ -176,7 +176,17 @@ end -----------------------------------------------------------------------------// ---@param job Job -local function handle_launch(job) ui.notify(utils.join(job:result())) end +local function notify_launch_error(job) + ui.notify(utils.join(job:stderr_result()), ui.ERROR, { timeout = 5000 }) +end + +-- `flutter emulators --launch` exits 0 even when the emulator dies during +-- startup, reporting the failure only on stderr. +---@param job Job +local function handle_launch(job) + if #job:stderr_result() > 0 then return notify_launch_error(job) end + ui.notify(utils.join(job:result())) +end ---@param emulator Device ---@param paths flutter.Paths @@ -195,17 +205,13 @@ function M.launch_emulator(emulator) local job if spec then job = Job:new({ command = spec.command, args = spec.args }) - job:after_failure( - vim.schedule_wrap( - function(j) ui.notify(utils.join(j:stderr_result()), ui.ERROR, { timeout = 5000 }) end - ) - ) else local args = { "emulator", "--launch", emulator.id } if emulator.cold_boot then table.insert(args, "--cold") end job = Job:new({ command = paths.flutter_bin, args = args }) job:after_success(vim.schedule_wrap(handle_launch)) end + job:after_failure(vim.schedule_wrap(notify_launch_error)) job:start() end) end diff --git a/tests/devices_spec.lua b/tests/devices_spec.lua index 39e79b9..73e15ce 100644 --- a/tests/devices_spec.lua +++ b/tests/devices_spec.lua @@ -149,25 +149,42 @@ INFO | Storing crashdata in: /tmp/android-ts/emu-crash-34.2.14.db, detection local devices local config local jobs + local notifications local modules = { "flutter-tools.devices", "flutter-tools.config", "flutter-tools.executable", + "flutter-tools.ui", "plenary.job", } local paths = { flutter_bin = "/sdk/bin/flutter" } local emulator = { id = "Pixel_8", name = "Pixel 8", system = "android", type = 1 } + local function finish(job, callback, stdout, stderr) + job.stdout, job.stderr = stdout or {}, stderr or {} + job.callbacks[callback](job) + vim.wait(100, function() return #notifications > 0 end) + end + before_each(function() jobs = {} + notifications = {} for _, name in ipairs(modules) do package.loaded[name] = nil end + package.loaded["flutter-tools.ui"] = { + ERROR = vim.log.levels.ERROR, + notify = function(msg, level) + if msg ~= "" then table.insert(notifications, { msg = msg, level = level }) end + end, + } package.loaded["plenary.job"] = { new = function(_, opts) - local job = { opts = opts, started = false } - function job:after_success() end - function job:after_failure() end + local job = { opts = opts, started = false, callbacks = {} } + function job:after_success(cb) self.callbacks.success = cb end + function job:after_failure(cb) self.callbacks.failure = cb end + function job:result() return self.stdout end + function job:stderr_result() return self.stderr end function job:start() self.started = true end table.insert(jobs, job) return job @@ -195,6 +212,35 @@ INFO | Storing crashdata in: /tmp/android-ts/emu-crash-34.2.14.db, detection assert.is_true(jobs[1].started) end) + it("should report a launch that exits cleanly but writes to stderr as an error", function() + devices.launch_emulator(emulator) + + finish(jobs[1], "success", {}, { "The Android emulator exited with code 1 during startup" }) + + assert.same({ + { + msg = "The Android emulator exited with code 1 during startup", + level = vim.log.levels.ERROR, + }, + }, notifications) + end) + + it("should show stdout of a clean launch", function() + devices.launch_emulator(emulator) + + finish(jobs[1], "success", { "No emulator found that matches 'Pixel_8'." }) + + assert.same({ { msg = "No emulator found that matches 'Pixel_8'." } }, notifications) + end) + + it("should report stderr when the flutter launch fails", function() + devices.launch_emulator(emulator) + + finish(jobs[1], "failure", {}, { "boom" }) + + assert.same({ { msg = "boom", level = vim.log.levels.ERROR } }, notifications) + end) + it("should use the command returned by a custom launcher", function() local received config.set({