Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions TESTS_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,19 @@ To test this in your `~/.config/nvim` configuration, try the suggested file stru
lua/example/module.lua
lua/spec/example/module_spec.lua
```

# Asynchronous testing

Tests run in a coroutine, which can be yielded and resumed. This can be used to
test code that uses asynchronous Neovim functionalities. For example, this can
be done inside a test:

```lua
local co = coroutine.running()
vim.defer_fn(function()
coroutine.resume(co)
end, 1000)
--The test will reach here immediately.
coroutine.yield()
Copy link
Contributor

@ipod825 ipod825 Jan 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just use async.util.sleep() (inside a.it)

Is there any more complex use case that justify this change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async.util.sleep and a.it are doing more or less the same behind the scenes. The point of this minimal example was to demonstrate a resuming a coroutine from a callback - something which would usually include things like autocommands or event handlers on Neovim jobs. But these are a bit more complex to set up and activate, so to keep the example simple I used a simple defer_fn.

--The test will only reach here after one second, when the deferred function runs.
```
52 changes: 28 additions & 24 deletions lua/plenary/busted.lua
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,9 @@ mod.run = function(file)
print("\n" .. HEADER)
print("Testing: ", file)

local ok, msg = pcall(dofile, file)
local loaded, msg = loadfile(file)

if not ok then
if not loaded then
print(HEADER)
print "FAILED TO LOAD FILE"
print(color_string("red", msg))
Expand All @@ -232,33 +232,37 @@ mod.run = function(file)
end
end

-- If nothing runs (empty file without top level describe)
if not results.pass then
if is_headless then
return vim.cmd "0cq"
else
return
coroutine.wrap(function()
loaded()

-- If nothing runs (empty file without top level describe)
if not results.pass then
if is_headless then
return vim.cmd "0cq"
else
return
end
end
end

mod.format_results(results)
mod.format_results(results)

if #results.errs ~= 0 then
print("We had an unexpected error: ", vim.inspect(results.errs), vim.inspect(results))
if is_headless then
return vim.cmd "2cq"
end
elseif #results.fail > 0 then
print "Tests Failed. Exit: 1"
if #results.errs ~= 0 then
print("We had an unexpected error: ", vim.inspect(results.errs), vim.inspect(results))
if is_headless then
return vim.cmd "2cq"
end
elseif #results.fail > 0 then
print "Tests Failed. Exit: 1"

if is_headless then
return vim.cmd "1cq"
end
else
if is_headless then
return vim.cmd "0cq"
if is_headless then
return vim.cmd "1cq"
end
else
if is_headless then
return vim.cmd "0cq"
end
end
end
end)()
end

return mod
69 changes: 69 additions & 0 deletions tests/plenary/async_testing_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
local Job = require "plenary.job"

local Timing = {}

function Timing:log(name)
self[name] = vim.loop.uptime()
end

function Timing:check(from, to, min_elapsed)
assert(self[from], "did not log " .. from)
assert(self[to], "did not log " .. to)
local elapsed = self[to] - self[from]
assert(min_elapsed <= elapsed, string.format("only took %s to get from %s to %s - expected at least %s", elapsed, from, to, min_elapsed))
end

describe("Async test", function()
it("can resume testing with vim.defer_fn", function()
local co = coroutine.running()
assert(co, "not running inside a coroutine")

local timing = setmetatable({}, {__index = Timing})

vim.defer_fn(function()
coroutine.resume(co)
end, 200)
timing:log("before")
coroutine.yield()
timing:log("after")
timing:check("before", "after", 0.1)
end)

it("can resume testing from job callback", function()
local co = coroutine.running()
assert(co, "not running inside a coroutine")

local timing = setmetatable({}, {__index = Timing})

Job:new {
command = "bash",
args = { "-ce", [[
sleep 0.2
echo hello
sleep 0.2
echo world
sleep 0.2
exit 42
]] },
on_stdout = function(_, data)
timing:log(data)
end,
on_exit = function(_, exit_status)
timing:log("exit")
--This is required so that the rest of the test will run in a proper context
vim.schedule(function()
coroutine.resume(co, exit_status)
end)
end
}:start()
timing:log("job started")
local exit_status = coroutine.yield()
timing:log("job finished")
assert.are.equal(exit_status, 42)

timing:check("job started", "job finished", 0.3)
timing:check("job started", "hello", 0.1)
timing:check("hello", "world", 0.1)
timing:check("world", "job finished", 0.1)
end)
end)